Search code examples
swiftuinavigationcontrollersubviewnib

Accessing UINavigationController from rootVC Subview (subview loaded from Nib)


The main ViewController is embedded in a UINavigationController subclass, and the VC has a subview that is loaded from a nib. The subview is called MenuView, and contains UIButtons that will link to other VCs.

To keep my main ViewController less unruly, I have put all these buttons into a subview that loads from a nib that animates the menu opening and closing.

However, I would like to present other view controllers from these, sometimes "Modally", sometimes "Show". What I have done seems to work, but I just want to know if this is alright, or if I have caused some unwanted effects that I'm unaware of (like a strong reference cycle that would cause a memory leak, or something). Or is there a better way to do this?

Some code: In MenuView.swift

class MenuView: UIView {
    var navigationController = CustomNavigationController()

    func combinedInit(){
        NSBundle.mainBundle().loadNibNamed("MenuViewXib", owner: self, options: nil)
        addSubview(mainView)
        mainView.frame = self.bounds
    }

    @IBAction func optionsAction(sender: AnyObject) {
        self.navigationController.performSegueWithIdentifier("presentOptions", sender: self)
    }

In ViewController.swift

        menuView.navigationController = self.navigationController as! CustomNavigationController

Solution

  • Short answer: No, it is not alright to access a view controller from within some view in the hierarchy, because that would break all the MVC rules written.


    UIView objects are meant to display UI components in the screen and are responsible for drawing and laying out their child views correctly. That's all there is. Nothing more, nothing less.

    You should handle those kind of interactions between views and controllers always in the controller in which the view in question actually belong. If you need to send messages from a view to its view controller, you can make use of either the delegate approach or NSNotificationCenter class.

    If I were in your shoes, I would use a delegate when view needs some information from its view controller. It is more understandable than using notification center as it makes it much easier to keep track of what's going on between. If the view controller needs some information from a view (in other words, the other way around), I'd go with the notification center.

    protocol MenuViewDelegate: class {
      func menuViewDidClick(menuView: MenuView)
    }
    
    class MenuView: UIView {
      var weak delegate: MenuViewDelegate?
    
      @IBAction func optionsAction(sender: AnyObject) {
        delegate?.menuViewDidClick(self)
      }
    }
    

    Let's look at what's going on at the view controller side:

    class MenuViewController: UIViewController, MenuViewDelegate {
    
      override func viewDidLoad() {
        ...
        self.menuView.delegate = self
      }
    
      func menuViewDidClick(menuView: MenuView) {
        navigationController?.performSegueWithIdentifier("presentOptions", sender: self)
      }
    }
    

    For more information about communication patterns in iOS, you might want to take a look at this great article in order to comprehend how they work.