swiftuiuinavigationcontrolleruinavigationbaruibarbuttonitem

Navigation Bar Items not shown when UIKit > SwiftUI > UIKit


When pushing from a SwiftUI view to a UIKit, navigation bar items are not present or not added.

I have added one item in the storyboard and one item in code, neither show up.

This is possibly a bug in SwiftUI, but I feel like this is probably a pretty common case and perhaps I am missing something?

There is some overlap with this question: Navigation Bar Items after push from SwiftUI to UIKit although I am not sure if they are the same or not

Results

Gif of simulator

Inputs

Storyboard

  1. No code, Storyboard only
  2. No code, Storyboard only
class MyHostingController: UIHostingController<SwiftUIView> {
 
    required init?(coder aDecoder: NSCoder) {    
        super.init(coder: aDecoder, rootView: SwiftUIView())
    }
}
struct SwiftUIView: View {
    var body: some View {
        NavigationLink(
            destination: MyUIKitView(),
            label: {
                Text("Go to MyUIKitView")
            })
    }
}

Glue between 3 & 4

struct MyUIKitView: UIViewControllerRepresentable {
    typealias UIViewControllerType = MyUIKitViewController

    func makeUIViewController(context: Context) -> MyUIKitViewController {
        let sb = UIStoryboard(name: "Main", bundle: nil)
        let myVC = sb.instantiateViewController(identifier: "MyUIKitViewController") as! MyUIKitViewController
        return myVC
    }
    
    func updateUIViewController(_ uiViewController: MyUIKitViewController, context: Context) {
        print("♻️")
    }
}
class MyUIKitViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 👋 ADD NAV BUTTON HERE  
        navigationItem.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
    }
}
  1. No code, Storyboard only

Solution

  • SwiftUI view used its own navigation controller and it ignores the UIKit navigation controller so the possible solution is to set your navigation bar to parent controller and set your navigation bar element by coding.

    like this

    class MyUIKitViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            // 👋 ADD NAV BUTTON HERE - must be in DispatchQueue
            DispatchQueue.main.async {
                self.parent?.navigationItem.title = "Your Title"
                self.parent?.navigationItem.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
            }
        }
    }
    

    Another solution is to use self.navigationController?.navigationBar.topItem?. This is the exact same as above. It gives a topItem.

    class MyUIKitViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            // 👋 ADD NAV BUTTON HERE - must be in DispatchQueue
            DispatchQueue.main.async {
                self.navigationController?.navigationBar.topItem?.title = "Your Title"
                self.navigationController?.navigationBar.topItem?.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
            }
        }
    }
    

    I used DispatchQueue for when the view is initiated then the parent is not set at this time. (You can use also use viewWillAppear instead of DispatchQueue.main.async)

    Edit

    If you don't want to add elements programmatically and if you need to use the same element from the storyboard then you can use this approch.

    class MyUIKitViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            // 👋 ADD NAV BUTTON HERE
            navigationItem.rightBarButtonItems?.append(UIBarButtonItem(systemItem: .camera))
            
            DispatchQueue.main.async { //<--Here
                self.navigationController?.navigationBar.topItem?.title = self.navigationItem.title
                self.navigationController?.navigationBar.topItem?.rightBarButtonItems = self.navigationItem.rightBarButtonItems
            }
        }
    }