Navigation Bar Items not shown when UIKit > SwiftUI > UIKit
Asked Answered
P

2

12

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
Pomp answered 13/2, 2021 at 14:50 Comment(0)
W
24

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
        }
    }
}
Wrac answered 16/2, 2021 at 12:17 Comment(6)
This works, thank you! I edited your answer a bit just because I didn't notice the DispatchMain bit and I thought it wasn't working. I will award bounty after the 16 hours is up! – Pomp
This is the code that I used: DispatchQueue.main.async { self.parent?.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.addPhoto(_:))) } – Pomp
Thanks! Helped a ton. My UIKit view that wasn't showing the buttons is sometimes shown from UIKit -> UIKit and sometimes from UIKit-SwiftUI-UIKit so had to do some conditional logic as well to detect the correct type of the previous view (UIViewController vs. UIHostingController) and use the appropriate navigationItem – Davisdavison
Hi Mark do you have a demo project for UIKit-SwiftUI-UIKit. I am struggling with the navigation bar .. The navigation item gets lost once I switch from UIKit > SwiftUI – Sheply
and how to customize navigation title color? – Miscue
in my case in the following code self.navigationController?.navigationBar.topItem?.rightBarButtonItems?.append rightBarButtonItems was nil so, instead I used: self.navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(systemItem: .camera) – Katzman
T
0

This is simple and possibly a more SwiftUI-centric approach I just used that is working really well. And it has the bonus of not having to do any awkward DispatchQueue work or to modify your existing code in the UIViewController.

Just FYI, in my case, this SwiftUIView is used inside a NavigationStack, so the navigation is owned by SwiftUI. The other answers might work better when the opposite is true (i.e., pushing SwiftUIView onto the stack in a UINavigationController).

struct SwiftUIView: View {
    var body: some View {
        NavigationLink(
            destination: ,
            label: {
                Text("Go to MyUIKitView")
            }
        )
    }

    @ViewBuilder
    func destinationView() -> some View {
        MyUIKitView()
            .navigationTitle("Your Title")
            .toolbar {
                ToolbarItem(placement: .topBarTrailing) {
                    Button(
                        action: { 
                            // Do something here
                        },
                        label: { Image(systemName: "camera") }
                    )
                }
            }
    }
}
Trivet answered 10/1 at 23:23 Comment(2)
I think perhaps this should be an answer for #57125102 and not here? – Pomp
Yeah, maybe a more precise answer for there. I'll add it there -- thanks for the suggestion. – Trivet

© 2022 - 2024 β€” McMap. All rights reserved.