iosswiftuiios13ios-navigationviewuiviewcontrollerrepresentable

Background color on Navigation Bar is not set when my SwiftUI based app is launched in landscape mode


I am working with a SwiftUI based app that relies on a NavigationView to transition from screens.

I have a requirement to set the background color on the navigation bar and have found code that makes this work most of the time.

When the app is launched in portrait mode, everything works properly across rotations.

However, when the app is launched in landscape mode, the bar is the default gray and only updates after the first rotation.

Below, I have the minimal amount of code to recreate my problem:

import SwiftUI

struct ContentView: View {
    var body: some View {
        return NavigationView {
            List {
                NavigationLink(destination: Text("A")) {
                    Text("See A")
                }
            }
            .background(NavigationConfigurator { navigationConfigurator in
                navigationConfigurator.navigationBar.barTintColor = .orange
            })
            .navigationBarTitle(Text(verbatim: "Home"), displayMode: .inline)
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct NavigationConfigurator: UIViewControllerRepresentable {
    var configure: (UINavigationController) -> Void = { _ in }

    func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
        UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController,
                                context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
        if let navigationController = uiViewController.navigationController {
            self.configure(navigationController)
            print("Successfully obtained navigation controller")
        } else {
            print("Failed to obtain navigation controller")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

How the app appears when launched in portrait mode:

enter image description here

...and rotated to landscape...

enter image description here

Finally, how it looks when launched in landscape mode.

enter image description here

I have also logged out the NavigationConfigurator and have found that when it launches in portrait mode, there are two calls made. The first one fails to find the navigation controller, but the second one does.

When I launch in landscape mode, only a single call is made which fails to find it. Upon rotation, it then finds it and successfully updates the color.

I did attempt to force redraws via @State management, but that was unsuccessful.

Short of locking the app to portrait mode, I have run out of ideas.


Solution

  • If it is only about bar tint color and not needed navigation controller for more then it is simpler to use appearance, as

    struct ContentView: View {
        init() {
            UINavigationBar.appearance().barTintColor = UIColor.orange
        }
        // .. your other code here
    

    This solution works fine with any initial orientation. Tested with Xcode 11.4.

    Alternate:

    If you still want access via configurator, the following solution (tested & worked) by my experience is more reliable

    struct NavigationConfigurator: UIViewControllerRepresentable {
        var configure: (UINavigationController) -> Void = { _ in }
    
        func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
            let controller = UIViewController()
            DispatchQueue.main.async {
                if let navigationController = controller.navigationController {
                    self.configure(navigationController)
                    print("Successfully obtained navigation controller")
                } else {
                    print("Failed to obtain navigation controller")
                }
            }
            return controller
        }
    
        func updateUIViewController(_ uiViewController: UIViewController,
                                    context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
        }
    }