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
Found the solution for this issue.
UIHostingController
is in fact just a normalUIViewController
(with some add-ons for SwiftUI). Which means everything which is available for aUIViewController
is as well available forUIHostingController
. So the solution for this is to set everything related to the navigation bar onUIHostingController
and not in the wrapped SwiftUI View.Also all navigations buttons work much better if directly set on
UIHostingController
. A good alternative is also to directly derive fromUIHostingController
and implement custom needed behavior there.To force
UIHostingController
to set up its navigation item we can pre-render it in a separate window:The window could even be cached for pre-rendering other views.
Update: the workaround seems to stop working on iOS 16.4.