skip to Main Content

When I use a UIHostingController to push a new SwiftUI.View to the navigation stack of an existing UIKit UIViewController the animation of the title in the navigation bar is broken. I tested in Xcode 12.0 on a pure new project.

Watch carefully the title "UIHostingController". You can see how the animation looks different from normal push animation, it just "appears" out of nothing and looks broken. The second animation happens already from SwiftUI.NavigationLink which looks fine.

Here is a link to the sample project if you want to try it out:
https://www.dropbox.com/s/mjkuzhpsb6yvlir/HostingControllerTest.zip?dl=0

See this GIF image: (open in another browser tab if you don’t the see GIF animation)

This is the code behind:

class ViewController: UIViewController {
    private let button = UIButton(frame: .zero)

    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "UIHostingController Title Test"
        self.view.backgroundColor = UIColor.white
        
        self.view.addSubview(self.button)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle("Push UIHostingController", for: .normal)
        button.addTarget(self, action: #selector(Self.pushVC), for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
            button.widthAnchor.constraint(equalTo: self.view.widthAnchor),
            button.heightAnchor.constraint(equalToConstant: 50)
        ])
    }
    
    @objc private func pushVC() {
        let vc = UIHostingController(rootView: Content())
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

struct Content: View {
    var body: some View {
        NavigationLink(destination: Content2()) {
            Text("Push NavigationLink")
        }
        .navigationTitle("UIHostingController")
    }
}

struct Content2: View {
    var body: some View {
        Text("Coming from NavigationLink")
            .navigationTitle("Native SwiftUI View")
    }
}

2

Answers


  1. Chosen as BEST ANSWER

    Found the solution for this issue. UIHostingController is in fact just a normal UIViewController (with some add-ons for SwiftUI). Which means everything which is available for a UIViewController is as well available for UIHostingController. So the solution for this is to set everything related to the navigation bar on UIHostingController and not in the wrapped SwiftUI View.

    let vc = UIHostingController(rootView: Content())
    vc.title = "My custom title"
    

    Also all navigations buttons work much better if directly set on UIHostingController. A good alternative is also to directly derive from UIHostingController and implement custom needed behavior there.


  2. To force UIHostingController to set up its navigation item we can pre-render it in a separate window:

    let window = UIWindow(frame: .zero)
    window.rootViewController = UINavigationController(rootViewController: hostingController)
    window.isHidden = false
    window.layoutIfNeeded()
    

    The window could even be cached for pre-rendering other views.

    Update: the workaround seems to stop working on iOS 16.4.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search