SwiftUI UIHostingController navigation title animation broken
Asked Answered
L

2

7

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")
    }
}
Loireatlantique answered 12/10, 2021 at 8:49 Comment(2)
I think I have the same problem, it's much easier to see if you turn on Debug/Slow Animations in the Simulator. The title (and back button and bar items) just "flash" in almost at the end, instead of fading up. Did you manage to fix it?Placidia
Yes, found the best possible solution for now. See my answer.Loireatlantique
L
10

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.

Loireatlantique answered 9/1, 2022 at 12:21 Comment(0)
S
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.

Sclater answered 7/2, 2022 at 13:54 Comment(3)
this workaround also causes duplicated overlapping navigation bars when dealing with navigation stacks pushing a bunch of these 'pre-loaded' windows.... :( Seems to be an issue in 16.6 (possible going back to 16.4)Duplessis
@Duplessis Yep, we also had to switch entirely to the NavigationStack as Apple seem to have abandoned supporting UIHostingController leading to more issues being introduced with every new iOS version.Sclater
@nikolay- we discovered that this was actually caused by an attributed string trying to render html in the SwiftUI View on the main thread while this was happening. We moved that attributed string html init out of the view and it resolved this bug.Duplessis

© 2022 - 2024 — McMap. All rights reserved.