Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIViewControllerRepresentable: Navigation Title and Bar Button Items Ignored in NavigationView

  • I have a UITableViewController subclass that I have wrapped in UIViewControllerRepresentable.
  • I have set the navigationItem.title and navigationItem.leftBarButtonItems in my view controller.
  • I present my UIViewControllerRepresentable instance as the destination of a SwiftUI NavigationLink within a SwiftUI NavigationView.
  • When the view is pushed onto the navigation stack, the table view appears but the title and bar button items do not.

What is happening here?

like image 802
josephap Avatar asked Dec 11 '19 23:12

josephap


2 Answers

Here's a UIKit solution that doesn't require making internal changes to your UIViewController and should only be called once, when the wrapper is added as parent.

struct MyViewControllerRepresentable: UIViewControllerRepresentable {

    class Coordinator {
        var parentObserver: NSKeyValueObservation?
    }

    func makeUIViewController(context: Self.Context) -> MyViewController {
        let viewController =  MyViewController()
        context.coordinator.parentObserver = viewController.observe(\.parent, changeHandler: { vc, _ in
            vc.parent?.title = vc.title
            vc.parent?.navigationItem.rightBarButtonItems = vc.navigationItem.rightBarButtonItems
        })
        return viewController
    }

    func updateUIViewController(_ uiViewController: MyViewController, context: Self.Context) {}

    func makeCoordinator() -> Self.Coordinator { Coordinator() }
}
like image 138
Devin Pitcher Avatar answered Sep 16 '22 14:09

Devin Pitcher


Solved!

Problem and Expectation SwiftUI uses a UINavigationController under the hood. So, if I push a UIViewController onto a SwiftUI NavigationView using UIViewControllerRepresentable, then I would expect the navigation item and toolbar items of that view controller to be used by said navigation controller. As I mention above, they are ignored.

Root Cause It turns out the title and items are ignored because my view controller’s parent is not the UINavigationController as expected. Rather, it’s parent is an intermediary wrapper view controller used by SwiftUI under the hood, which is in turn pushed onto the navigation controller. It’s ignoring the title and items because the navigation controller is asking the wrapper for its items (which has none), rather than my view controller.

UIKit Solution So, if you want to set the title or bar button items or toolbarItems from your UIKit view controller, then you need to set them on it’s parent as such:

self.parent?.navigationItem.title = "My Title"

Furthermore, you cannot do this from viewDidLoad, because the view controller does not appear to have been wrapped by the SwiftUI parent by that time. You have to do it in viewWillAppear.

SwiftUI Solution You can also set the title and bar buttons from SwiftUI. On your UIViewControllerRepresentable instance, just add .navigationBarTitle and leading/trailing items as you normally would. Then you can have the buttons talk to your view controller from within your UIViewControllerRepresentable implementation.

like image 24
josephap Avatar answered Sep 17 '22 14:09

josephap