Question

I have a complex iPad view that I manage by having several view controllers. I previously (before iOS6/XCode 4.5) did this by allocating my view controllers in code, and hooked up the various views to them though links to the master view.

What I would like to do is use the new container views to embed the view controllers in the storyboard file. I don't seem to be able to make an IBOutlet link to the embedded view controller to the master controller.

Is it possible to do this? Or to retrieve the embedded controller via a tag or something in the code?

This question is SPECIFICALLY about using container views

Was it helpful?

Solution

I'm not sure what you mean by "retrieve the embedded controller". When you want to use a controller you use the UIStoryboard method instantiateViewControllerWithIdentifier:, using the identifier that you give to the controller in IB. You can also use the performSegueWithIdentifier:sender: method (which also instantiated the view controller). You should check out the "Using View Controllers in Your App" section in the Apple docs. It also makes reference to the fact that child view controllers are instantiated at the same time as the container controller.

After edit: If you embed a container view in another view controller, that embedded view's controller can be referenced from the containing controller with self.childViewControllers (which will be an array, so if there is just one, you can get it with lastObject).

OTHER TIPS

Another option for some cases is to capture the embedded controller using -prepareForSegue:sender:.

For example, if I have a UINavigationController embedded within a CustomContainerViewController, I can name the embed segue embedContentStack in the storyboard and capture it in CustomContainerViewController via

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"embedContentStack"]) {
        // can't assign the view controller from an embed segue via the storyboard, so capture here
        _contentStack = (UINavigationController *)segue.destinationViewController;
    }
}

Here is another thread about it: Access Container View Controller from Parent iOS

They propose to keep a reference in prepareForSegue or search for the embedded viewController in self.childViewControllers

Note of Caution

Before proceeding to use an answer to this question, you may wish to reflect whether the embedded things really need to be view controllers.

Eg, if you're embedding a UICollectionViewController subclass, could you instead embed a UICollectionView subclass? Or, even better, could you embed a UIView subclass that hides away the UICollectionView behind a simple ViewModel?

In the code base I'm currently working on, I'm embedding two view controllers in to another view controller. Both could fairly easily be plain views instead, and could then be more easily bound to in the storyboard, without this messy code.

Unfortunately, they are currently view controllers and I'm not in a position to simplify them in to plain views right now, so this will have to do.

Background

I'm using the approach of picking up the embed segue in prepare(for segue:, sender:) as suggested by Playful Geek here.

I wanted to show the swift I'm using for this, as it seems to be fairly tidy…

class EditionLandingViewController: UIViewController {
    fileprivate var titlesView: SectionTitlesViewController!
    fileprivate var sectionsView: SectionsViewController!
}

//MARK:-

extension EditionLandingViewController {
    private enum SegueId: String {
        case embedTitles
        case embedSections
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        super.prepare(for: segue, sender: sender)

        guard
            let segueRawId = segue.identifier,
            let segueId = SegueId(rawValue: segueRawId)
            else { return }

        switch segueId {
        case .embedTitles:
            self.titlesView = segue.destination as! SectionTitlesViewController

        case .embedSections:
            self.sectionsView = segue.destination as! SectionsViewController
        }
    }
}

Discussion

I've chosen to name segues as action methods.

Using an enum cases for segue identifiers means you've got the compiler and tooling on your side, so its much harder to get a segue name wrong.

Keeping the segue ids in a private enum within the extension scope seems appropriate in this case as these segues are not needed anywhere else (they can't be performed, for example).

I'm using implicitly unwrapped types for the embedded view controllers because (in my case anyway) it's a logic error if they are missing.

Similarly, I'm also happy to force cast the destination view controller types. Again, it would be a logic error if these types are not the same.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top