Michael Wells
Michael Wells

Reputation: 596

How do you access UIViewControllerRepresentable NavigationBar property?

In SwiftUI if you are transitioning using a NavigationLink() into a UIViewControllerRepresentable how would you; say, add buttons or change the title property on the navigationbar.

This is what I am doing right now:

import SwiftUI

/// Controls the actual action performed by the button upon taps.
struct CatagoryButton: View {

    @State var isPresenting :Bool = false

    var company : Company?
    var text : String

    var body: some View {

        NavigationLink(destination: UIKitWrapper(company: self.company, storyboardPointer: self.text)
            .navigationBarTitle(self.text)
            .edgesIgnoringSafeArea(.all),
                       isActive: self.$isPresenting,

                       label: {
            Button(action: {
                self.isPresenting.toggle()

            }){

                ZStack {

                    ButtonShadowLayer(text: text)

                    GradientBackground()
                        .mask(ButtonBaseLayer())

                    CircleAndTextLayer(text: text)
                }
            }
        })
    }
}

Here is the struct for my representable.

import SwiftUI

/// Wraps UIKIT instance in a representable that swiftUI can present.
struct UIKitWrapper: UIViewControllerRepresentable {

    //Specify what type of controller is being wrapped in an associated type.
    typealias UIViewControllerType = UIViewController

    //Company property passed from parent view. Represents the company the user selected from main view.
    private var company : Company

    //Determines which viewcontroller will be presented to user. This string corresponds to the name of the storyboard file in the main bundle.
    private var storyboardPointer : String

    init(company: Company?, storyboardPointer: String) {

        guard let company = company else {fatalError()}

        self.company = company
        self.storyboardPointer = storyboardPointer
    }

    func makeUIViewController(context: Context) -> UIViewControllerType {

        //Find user defined storyboard in bundle using name.
        let storyboard = UIStoryboard(name: storyboardPointer, bundle: .main)

        //Downcast returned controller to protocol AccessControllerProtocol. This step is required because we are not sure which storyboard will be accessed. Potential storyboard controllers that can be called all conform to this protocol. 
        //FIXME: Remove fatalError and create error enum asap.
        guard let viewController = storyboard.instantiateInitialViewController() as? AccessControllerProtocol else { fatalError() }

        //Assign user selected company object to instance property on incoming viewController.
        viewController.company = company

        //Return UINavigationController with storyboard instance view controller as root controller.
        return viewController
    }

    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {

    }
}

Finally, here is one of the classes that use the representable.

import UIKit

class OrdersViewController: UIViewController, AccessControllerProtocol {

    var company : Company!
    @IBOutlet var companyNameLabel : UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        setBackgroundColor()
        companyNameLabel.text = company.name
        self.navigationController?.navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.tapRightBarButton))]
    }

    func setBackgroundColor(){

        let backgroundGradient = BackgroundGradientSetter()
        let viewWithGradient = backgroundGradient.setGradientToView(with: [DarkBlueHue_DEFAULT,LightBlueHue_DEFAULT], size: view.bounds)

        view.addSubview(viewWithGradient)
        view.sendSubviewToBack(viewWithGradient)
    }

    @objc func tapRightBarButton(){

    }
}

No matter what I do this button doesn't show up. I'm not sure if I need to put this in a makeCoordinator() or if there is just something I am missing. If anyone has insight please let me know!

Upvotes: 5

Views: 8254

Answers (2)

Peter Shaburov
Peter Shaburov

Reputation: 1667

If it isn't available in viewDidLoad, try calling your setupNavigation() in viewWillAppear()

Upvotes: 0

Asperi
Asperi

Reputation: 258345

In your case navigationController is not available yet on viewDidLoad, try instead as in below demo module

Tested & works with Xcode 11.2 / iOS 13.2

demo

class MyUIController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.navigationController?.navigationBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(self.onAdd(_:)))
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // might be needed to remove injected item here
    }

    @objc func onAdd(_ sender: Any?) {
        print(">> tapped add")
    }
}

struct MyInjector: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<MyInjector>) -> MyUIController {
        MyUIController()
    }

    func updateUIViewController(_ uiViewController: MyUIController, context: UIViewControllerRepresentableContext<MyInjector>) {
    }
}

struct DemoNavigationBarUIButton: View {
    var body: some View {
        NavigationView {
            MyInjector()
                .navigationBarTitle("Demo")
        }
    }
}

struct DemoNavigationBarUIButton_Previews: PreviewProvider {
    static var previews: some View {
        DemoNavigationBarUIButton()
    }
}

Upvotes: 1

Related Questions