Using iOS 16.0 and Xcode 14.2
I am super new to SwiftUI and it honestly still hasn't really clicked yet, so this code probably is very inefficient. But basically I just want a button that when you press it, you go to another view. But when I try it like this, the preview crashes. Is there a better way to do this? And also what is causing the preview to crash? I've tried a bunch of different things and it either causes the preview to crash or the button just doesn't do anything.
Homescreen (had other buttons that were working that I redacted for clarity)
import SwiftUI
struct WordAndArrows: View {
#State private var showLibrary = false
var body: some View {
LibraryButton (action: {
self.navigateToLibraryScreen()
})
}
VStack {
if showLibrary {
LibraryView()
}
}
}
func navigateToLibraryScreen() {
self.showLibrary = true
}
}
struct WordAndArrows_Previews: PreviewProvider {
static var previews: some View {
ZStack {
GradientBackground()
WordAndArrows()
}
}
}
Library Button View
import SwiftUI
struct LibraryButton: View {
var action: () -> Void
var body: some View {
//Button("Check out my library") {
Button(action: action) {
Text("Library")
}
.padding()
.background(Color("Lime"))
.clipShape(Capsule())
.buttonStyle(SquishButtonStyle(fadeOnPress: false))
.font(Font.custom("Quicksand-Bold", size: 15))
.foregroundColor(Color("Indigo"))
.shadow(color: .gray, radius: 2.5, x: 0, y: 5)
}
}
I tried:
Making a navigatetolibraryscreen function that would be triggered when the buttn was hit
Using a navigationLink
3 Wrapping the navigation link in a Vstack
And it either caused the button to not do anything or the preview to crash
My app has a SwiftUI List with cells that can be swiped (leading, trailing).
Problem:
Say, a cell is swiped so that the buttons are visible. Then the app is de-activated in this state, e.g. by a switch to another app, by locking the screen, etc.
When the app later is re-activated, the earlier swiped cell is still swiped, although the user might no longer be aware of the reason.
It would thus be better to undo the swipe by code, when the app is deactivated.
Question:
Is this possible?
It is a but of a brute-force approach but redrawing with this approach works.
import SwiftUI
#available(iOS 15.0, *)
struct ResetSwipeView: View {
#Environment(\.scenePhase) var scenePhase
#State var id: UUID = .init()
var body: some View {
List(1...10){n in
Text(n, format: .number)
.swipeActions {
Button {
print("Button :: \(n)")
} label: {
Text("print")
}
}
}.id(id)
.onChange(of: scenePhase, perform: { newValue in
if newValue == .inactive{
id = .init()
}
})
}
}
#available(iOS 15.0, *)
struct ResetSwipeView_Previews: PreviewProvider {
static var previews: some View {
ResetSwipeView()
}
}
I found 2 unexpected (for me) behaviors of Picker in SwiftUI.
The default style for Picker on iOS inside the List view seems to be .menu and it does not take any tint color at all (fair enough). However, forcing style with .pickerStyle(.menu) changes picker's color as expected… almost.
Changing the tint color of the parent's view does not have any effect on this picker.
Do you have any idea why does it happen?
Xcode 14.1 RC, iOS 16.1
struct PickerTintColorTest: View {
let colors: [Color] = [.purple, .green, .orange]
var colorList: some View {
ForEach(colors, id: \.self) { color in
Text(color.description)
}
}
#State private var selectedColor: Color = .green
var body: some View {
List {
Picker("Default style", selection: $selectedColor) { colorList } // <- doesn't have a tint color at all
Picker("Menu style", selection: $selectedColor) { colorList }
.pickerStyle(.menu) // <- forcing to .menu style gives picker a tint color BUT it doesn't update
Button("Action") { } // <- tint as expected, updates as expected
}
.tint(selectedColor)
}
}
Not initial screenshot. It's after changing picker value.
I just started coding in SwiftUI and came across a problem. I need to give different colors to the background of the navigation bar (NavigationView). The colors will change as I go from one view to the next. I need to have this working for navigationBarTitleDisplayMode being "inline".
I tried the solutions presented in:
SwiftUI update navigation bar title color
but none of these solutions work fully for what I need.
The solution in this reply to that post works for inline:
Using UIViewControllerRepresentable. Nevertheless, when we first open the view it will show the color of the previous view for one second, before changing to the new color. I would like to avoid this and have the color displayed as soon as everything appears on screen. Is there a way to do this?
This other solution will not work either: Changing UINavigation's appearance in init(), because when I set the background in init(), it will change the background of all the views in the app. Again, I need the views to have different background colors.
I tried something similar to this solution: Modifying Toolbar, but it does not allow me to change the color of the navigation bar.
The other solution I tried was this: Creating navigationBarColor function, which is based on: NAVIGATIONVIEW DYNAMIC BACKGROUND COLOR IN SWIFTUI. This solution works for navigationBarTitleDisplayMode "large", but when setting navigationBarTitleDisplayMode to "inline", it will show the background color of the navigation bar in a different color, as if it was covered by a gray/transparent layer. For example, the color it shows in "large" mode is:
Red color in large mode
But instead, it shows this color:
Red color in inline mode
Finally, I tried this solution: Subclassing UIViewController and configuring viewDidLayoutSubviews(), but it did not work for what I want it either.
The closest solutions for what I need are 1. and 4., but they still do not work 100%.
Would anybody know how to make any of these solutions work for navigationBarTitleDisplayMode inline, being able to change the background color of the navigation bar in different layouts, and showing the new color once the view is shown (without delays)?
Thank you!
By the way, I am using XCode 12.5.
Here is the sample code that I am using, taking example 4. as a model:
FirstView.swift
import SwiftUI
struct FirstView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
GeometryReader { metrics in
VStack {
Text("This is the first view")
NavigationLink(destination: SecondView(), tag: "SecondView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "SecondView"
print("Go to second view")
}) {
Text("Go to second view")
}
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift
On this screen, if I use
.navigationBarTitleDisplayMode(.large)
the color will be displayed properly: Navigation bar with red color
But using
.navigationBarTitleDisplayMode(.inline)
there is a blur on it: Navigation bar with some sort of blur over red color
import SwiftUI
struct SecondView: View {
#State private var selection: String? = nil
var body: some View {
GeometryReader { metrics in
VStack {
Text("This is the second view")
NavigationLink(destination: ThirdView(), tag: "ThirdView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "ThirdView"
print("Go to third view")
}) {
Text("Go to third view")
}
}
}
.navigationBarColor(backgroundColor: Color.red, titleColor: .black)
.navigationBarTitleDisplayMode(.inline)
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
ThirdView.swift
This view displays the color properly as it is using
.navigationBarTitleDisplayMode(.large)
But if changed to
.navigationBarTitleDisplayMode(.inline)
it will show the blur on top of the color as well.
import SwiftUI
struct ThirdView: View {
var body: some View {
GeometryReader { metrics in
Text("This is the third view")
}
.navigationBarColor(backgroundColor: Color.blue, titleColor: .black)
.navigationBarTitleDisplayMode(.large)
}
}
struct ThirdView_Previews: PreviewProvider {
static var previews: some View {
ThirdView()
}
}
NavigationBarModifierView.swift
import SwiftUI
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: Color, titleColor: UIColor?) {
self.backgroundColor = UIColor(backgroundColor)
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = UIColor(backgroundColor)
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.shadowColor = .clear
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = titleColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: Color, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
NOTE TO THE MODERATORS: Please, do not delete this post. I know similar questions were asked before, but I need an answer to this in particular which was not addressed. Please read before deleting indiscriminately, I need this for work. Also, I cannot ask questions inline in each of those solutions because I do not have the minimum 50 points in stackoverflow required to write there.
I think I have what you want. It is VERY touchy... It is a hack, and not terribly robust, so take as is...
I got it to work by having your modifier return a clear NavBar, and then the solution from this answer works for you. I even added a ScrollView to ThirdView() to make sure that scrolling under didn't affect in. Also note, you lose all of the other built in effects of the bar like translucency, etc.
Edit: I went over the code. The .navigationViewStyle was in the wrong spot. It likes to be outside of the NavigaionView(), where everything else needs to be inside. Also, I removed the part of the code setting the bar color in FirstView() as it was redundant and ugly. I hadn't meant to leave that in there.
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: Color, titleColor: UIColor?) {
self.backgroundColor = UIColor(backgroundColor)
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = .clear // The key is here. Change the actual bar to clear.
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.shadowColor = .clear
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
UINavigationBar.appearance().tintColor = titleColor
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View {
func navigationBarColor(backgroundColor: Color, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
struct FirstView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
GeometryReader { _ in
VStack {
Text("This is the first view")
NavigationLink(destination: SecondView(), tag: "SecondView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "SecondView"
print("Go to second view")
}) {
Text("Go to second view")
}
}
.navigationTitle("First")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .red, titleColor: .black)
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SecondView: View {
#State private var selection: String? = nil
var body: some View {
VStack {
Text("This is the second view")
NavigationLink(destination: ThirdView(), tag: "ThirdView", selection: $selection) {
EmptyView()
}
Button(action: {
self.selection = "ThirdView"
print("Go to third view")
}) {
Text("Go to third view")
}
}
.navigationTitle("Second")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .blue, titleColor: .black)
}
}
struct ThirdView: View {
var body: some View {
ScrollView {
ForEach(0..<50) { _ in
Text("This is the third view")
}
}
.navigationTitle("Third")
.navigationBarTitleDisplayMode(.inline)
.navigationBarColor(backgroundColor: .green, titleColor: .black)
}
}
iOS 16
Since this version of SwiftUI, there is a dedicated modifier for setting any toolbar background color (including the navigation bar):
Xcode 14 beta 5 (Not working 🤦🏻♂️, waiting for beta 6...)
.toolbarBackground(.red, for: .navigationBar)
Xcode 14 beta 1,2,3,4
.toolbarBackground(.red, in: .navigationBar)
It works perfectly in in inline mode and also animates between modes.
For my custom view the following code worked well.
struct HomeView: View {
init() {
//Use this if NavigationBarTitle is with Large Font
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.systemIndigo]
//Use this if NavigationBarTitle is with displayMode = .inline
UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.systemIndigo]
UINavigationBar.appearance().backgroundColor = UIColor.clear
UINavigationBar.appearance().barTintColor = UIColor(Color(red: 32 / 255, green: 72 / 255, blue: 63 / 255))
}
var body: some View {
NavigationView {
ZStack {
...
...
...
}
.padding(.zero)
.navigationTitle("Feedbacks")
}
}
}
and result is like that:
Here is a bit hacky solution, but it works for me (as of iOS 15) both for .large and .inline display modes.
import SwiftUI
enum Kind: String, CaseIterable {
case checking
case savings
case investment
}
struct PaddedList: View {
#Binding var name: String
#Binding var kind: Kind
var body: some View {
NavigationView {
List {
TextField("Account name", text: $name)
Picker("Kind", selection: $kind) {
ForEach(Kind.allCases, id: \.self) { kind in
Text(kind.rawValue).tag(kind)
}
}
.listRowSeparatorTint(.red)
Spacer()
}
.padding(.top, 1) // note top 1 padding!
.background(.green) // the color "bleeds" through
.navigationBarTitle("Navigation Bar")
}
}
}
struct PaddedList_Previews: PreviewProvider {
static var previews: some View {
PaddedList(name: .constant(""), kind: .constant(.checking))
}
}
I'm trying to simply add a DatePicker in SwiftUI to a navigation bar, this is my code:
struct ContentView: View {
#State var selectedDate: Date = Date()
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
}
.navigationBarItems(
leading: DatePicker(selection: $selectedDate, displayedComponents: .date) {}
)
}
}
}
This works fine on iOS but on the iPad simulator when I click the date picker nothing happens and the console prints some layout constraint error and this:
UIDatePicker 0x7fc3511141d0 is being laid out below its minimum width
of 280. This may not look like expected, especially with larger than
normal font sizes.
It looks like the leading position in the navigation bar is occupied by the sidebar button. As an alternative to .navigationBarItems, you can use toolbar and specify a placement:
struct ContentView: View {
#State var selectedDate = Date()
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
}
.toolbar {
ToolbarItem(placement: .principal) {
DatePicker(selection: $selectedDate, displayedComponents: .date) {}
}
}
}
}
}
Note: I used the .principal placement because it seems there's no enough space to render a DatePicker as .navigationBarLeading. However, feel free to try other placements.