IBOutlet link to embedded container view controller
Asked Answered
E

5

36

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 UIContainerView 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

Ebby answered 1/10, 2012 at 15:49 Comment(1)
This is the question of the age for storyboard. It astounds me there isn't 100s of questions on this!Glossotomy
P
22

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).

Premer answered 1/10, 2012 at 15:58 Comment(6)
I am performing a push segue to get to the "master view controller". Embedded in that view controller is a container view, pointing to another different View Controller subclass. At some point in the execution, I will need to pass data between the master and the contained view controllers. Are you familiar at all with the container view element that was introduced in XCode 4.5?Ebby
@DanF, I'm not sure what you mean by "container view element". Are you talking about custom container controllers, or is this something else?Premer
I am talking about the object in the XCode 4.5 interface builder that is called "Container View" that allows you to embed storyboard-built view controllers in another view controller. If you don't know what that is, you can't really answer my question, unless you otherwise know how to link objects in two different scenes in a storyboardEbby
Perfect! Exactly the information I was looking for. Container views seemed kind of useless to me if you couldn't reference them from the containing view.Ebby
What the heck do you do when there's more than one??Glossotomy
Why was this answer accepted? the correct answer is NO. you can't reference contained view controllers, it is unclear why apple does not support it. This answer just works around the issue, which is fine, but it does not answer the question that was asked.Kelcy
H
73

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;
    }
}
Hiller answered 11/12, 2012 at 19:34 Comment(1)
Agree with @prodos; this should be accepted, it's a far more robust way of capturing the reference than looking through the child view controllers array.Emulate
P
22

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).

Premer answered 1/10, 2012 at 15:58 Comment(6)
I am performing a push segue to get to the "master view controller". Embedded in that view controller is a container view, pointing to another different View Controller subclass. At some point in the execution, I will need to pass data between the master and the contained view controllers. Are you familiar at all with the container view element that was introduced in XCode 4.5?Ebby
@DanF, I'm not sure what you mean by "container view element". Are you talking about custom container controllers, or is this something else?Premer
I am talking about the object in the XCode 4.5 interface builder that is called "Container View" that allows you to embed storyboard-built view controllers in another view controller. If you don't know what that is, you can't really answer my question, unless you otherwise know how to link objects in two different scenes in a storyboardEbby
Perfect! Exactly the information I was looking for. Container views seemed kind of useless to me if you couldn't reference them from the containing view.Ebby
What the heck do you do when there's more than one??Glossotomy
Why was this answer accepted? the correct answer is NO. you can't reference contained view controllers, it is unclear why apple does not support it. This answer just works around the issue, which is fine, but it does not answer the question that was asked.Kelcy
M
3

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

Memento answered 4/11, 2014 at 11:17 Comment(0)
J
0

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.

Janiuszck answered 25/4, 2018 at 7:59 Comment(0)
C
0

Swift version of the top-voted Answer. Years later, Interface Builder still does not seem to support dragging IBOutlets to embedded Container Views.

Instead, set the outlets in prepare(for:sender:):

@IBOutlet var someViewController: SomeViewController!

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "EmbedSomeViewController", let destination = segue.destination as? SomeViewController {
        someViewController = destination
    }
}

You must also set up the UIContainerView on your Storyboard. Xcode will generate an embed segue automatically; set the segue's Identifier.

Set Identifier on Storyboard

Cresset answered 21/9, 2021 at 1:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.