2

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.

CodeBender
  • 35,668
  • 12
  • 125
  • 132

1 Answers1

5

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>) {
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 1
    Thanks @asperi I went with your alternate fix as it allowed me to achieve the desired effect in the full app. – CodeBender Apr 24 '20 at 15:06
  • Just tried your first approach using the `.appearance()` proxy in a Views init with Xcode 12 beta 3 and it doesn’t seem to work. – ixany Aug 03 '20 at 17:31
  • 1
    @ixany, it was already used with SwiftUI 2.0 and worked - see example of usage https://stackoverflow.com/a/62737287/12299030. Just retested with Xcode 12b3 / iOS 14. – Asperi Aug 03 '20 at 17:45
  • Can confirm that the `UIViewControllerRepresentable` solution does work, however using the proxy in `init()` doesn’t have any effect for me (Xcode 12b3 / iOS 14). – ixany Aug 03 '20 at 19:56
  • I apologise — you’re right, it does work with `.appearance()`! I have now idea why it doesn’t worked in the first place for me but now it does. Thank you for your ongoing support @Asperi! – ixany Aug 03 '20 at 20:36