iosswiftswiftuiuinavigationcontroller

SwiftUI - NavigationTitle appears after delay


I am trying to understand why when I push UIHostingViewController with SwiftUI View in it to my navigation stack, there's a slight delay when the navigation title appears, as you can see from this demo:

enter image description here

Code:

struct SwiftUIView: View {
  var body: some View {
    EmptyView()
          .navigationTitle("SwiftUI View")
  }
}

Versus using UIViewController where the title is visible right away:

enter image description here

One of the solutions that I can think of is to add .title before pushing UIHostingViewController to the stack, but I am wondering if there's a way to fix it from SwiftUI component?

Here's the project that illustrates what I am describing: https://github.com/ignotusverum/NavigationTitlesTesting

And full code:

class ViewController: UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white
        
        let button = UIButton(type: .system)
        button.addTarget(self,
                         action: #selector(buttonTapped),
                         for: .touchUpInside)
        button.setTitle("Push SwiftUI View", for: .normal)
        
        let button2 = UIButton(type: .system)
        button2.addTarget(self,
                         action: #selector(button2Tapped),
                         for: .touchUpInside)
        button2.setTitle("Push UIViewController", for: .normal)

        let stackView = UIStackView(arrangedSubviews: [button, button2])
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.heightAnchor.constraint(equalToConstant: 120),
            stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
        
        self.view = view
    }
    
    @objc func buttonTapped() {
        let hostingController = UIHostingController(rootView: SwiftUIView())
        
        navigationController?.pushViewController(hostingController,
                                                 animated: true)
    }
    
    @objc func button2Tapped() {
        let viewController = UIViewController()
        viewController.view.backgroundColor = .white
        viewController.title = "UIViewController"
        navigationController?.pushViewController(viewController,
                                                 animated: true)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Navigation Test"
        navigationItem.backButtonTitle = "Back"
    }
}

struct SwiftUIView: View {
  var body: some View {
    EmptyView()
          .navigationTitle("SwiftUI View")
  }
}

PS:

It seems like this issue arises because the large title updates its appearance asynchronously. The delay occurs when the UIHostingController reads the navigation title from the SwiftUI view's environment.


Solution

  • It seems to be an issue when using the UIHostingViewController. Set the title of the hosting view controller before pushing it:

    let vc = UIHostingController(rootView: SomeSwiftUiScreen())
    
    // fix for navigation bar title glitch when pushing a SwiftUI screen from UIKit
    vc.title = "My Title"
    
    navigationController?.pushViewController(vc, animated: true)