49

I'm trying to use storyboard and get things working properly. I've added a a Container View to one of my existing views. When I try to add a reference to this in my view controller .h file (ctrl-drag), I get a IBOutlet UIView *containerView. How do I get a reference to the container view's view controller instead? I need the container view controller so I can set it's delegate to my view's controller so they can "talk" to each other.

I have my story board setup as:

enter image description here

And its referenced in my .h file as:

enter image description here

Notice in the .h that is is a UIView, not my InstallViewController for the view. How do I add a reference to the view controller? I need to be able to set its delegate.

DAXaholic
  • 28,212
  • 5
  • 58
  • 67
Justin808
  • 19,126
  • 41
  • 143
  • 241
  • Here's the final, ultimate, last-word, maintained, tutorial on that! https://stackoverflow.com/a/23403979/294884 There are some subtleties you need to know about, they are all explained there in detail and clearly. – Fattie Aug 17 '17 at 13:49

5 Answers5

70

There is another solution by specifying an identifier for the embed segue(s) and retrieve the corresponding view controllers in method prepareForSegue:

The advantage of this way is that you needn't rely on a specific order in which your child view controllers are added due to the fact that each child view controller is embedded via an unique segue identifier.

Update 2013-01-17 - Example

- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
    // -- Master View Controller
    if ([segue.identifier isEqualToString:c_SegueIdEmbedMasterVC])
    {
        self.masterViewController = segue.destinationViewController;
        // ...
    }
    // -- Detail View Controller
    else if ([segue.identifier isEqualToString:c_SegueIdEmbedDetailVC])
    {
        self.detailViewController = segue.destinationViewController;
        // ...
    }
}

c_SegueIdEmbedMasterVC & c_SegueIdEmbedDetailVC are constants with the corresponding ID of the segue IDs defined in the storyboard.

DAXaholic
  • 28,212
  • 5
  • 58
  • 67
  • 2
    Sorry for the late update. I'm sure you managed the problem by yourself in the meantime but eventually the example code helps other ones in the future. – DAXaholic Jan 18 '13 at 05:28
  • 2
    God answer. I Tried that trick before, and only after reading this answer I realized that it only works if you setup Segue Identifiers. However I would use isKindOfClass: instead. – Yariv Nissim Apr 23 '13 at 18:58
  • Why are there two view controllers (detail and master)? Isn't it just the one view controller in the container? What other segue are you referring to? – shim Jul 04 '14 at 05:10
  • 1
    @shim: In my example project I had a kind of split view with a master and a detail view controller hosted in 2 separate container views - therefore the two view controllers. – DAXaholic Jul 07 '14 at 10:55
  • Its a shame that this is the best way of getting a reference to the child view controller. The whole point of the storyboards is to reduce boilerplate code like this. I could add the child view controller in `viewDidLoad` in fewer lines of code and it would be more maintainable than this. Anyone know if this fixed in XCode 6? – Robert Jul 08 '14 at 16:19
14

When you add a container view the xcode calls the UIViewController method addChildViewController:

In your case, you can get the container ViewController looking for it on the SplashViewController's list of childViewControllers, something like this:

for (UIViewController *childViewController in [self childViewControllers])
{
    if ([childViewController isKindOfClass:[InstallViewController class]])
    {
        //found container view controller
        InstallViewController *installViewController = (InstallViewController *)childViewController;

        //do something with your container view viewcontroller

        break;
    }
}

I had the same doubt yesterday :)

vfranchi
  • 433
  • 3
  • 12
  • 1
    I am trying to do something similar and what i don't get is the "vc" in InstallViewController *installViewController = (InstallViewController *)vc; – Pantelis Proios Dec 03 '12 at 08:29
  • You are right, it should've been "childViewController". I've just fixed in the post. Sorry for taking that much to reply. – vfranchi Mar 29 '13 at 22:52
  • 1
    This approach feels a bit like a "workaround" to me. Doesn't it? – Javier Quevedo May 09 '13 at 10:01
  • Well, it did the trick :) Does the prepareForSegue method is called when a Container View is added to another view? – vfranchi Jun 14 '13 at 17:51
2

The answer of Vitor Franchi is correct but could be more performant and convenient. Especially when accessing the child view controller several times.

Create a readonly property

@interface MyViewController ()
@property (nonatomic, weak, readonly) InstallViewController *cachedInstallViewController;
@end

Then create a convenient getter method

- (InstallViewController *)installViewController
{
    if (_cachedInstallViewController) return _cachedInstallViewController;

    __block InstallViewController *blockInstallViewController = nil;
    NSArray *childViewControllers = self.childViewControllers;
    [childViewControllers enumerateObjectsUsingBlock:^(id childViewController, NSUInteger idx, BOOL *stop) {

        if ([childViewController isMemberOfClass:InstallViewController.class])
        {
            blockInstallViewController = childViewController;
            *stop = YES;
        }
    }];

    _cachedInstallViewController = blockInstallViewController;

    return _cachedInstallViewController;
}

From now on access the child view controller that way

[self.installViewController doSomething];
Martin Stolz
  • 4,856
  • 3
  • 18
  • 19
0
UIView* viewInsideOfContainer = installerView.subviews[0];

Will give you the UIView inside of the UIViewController that your controller UIView references. You can cast the subview to any type that inherits from UIView.

John Verco
  • 1,254
  • 8
  • 6
0

If the nib is loaded it will call addChildViewController as part of the initialisation process

so a performant solution could be also to overwrite

- (void)addChildViewController:(UIViewController *)childController

there you can catch your childController e.g. by comparing its Class and assign it to a property / ivar

-(void)addChildViewController:(UIViewController *)childController
{
    [super addChildViewController:childController];

    if([childController isKindOfClass:[InstallViewController class]])
    {
        self.installViewController = (InstallViewController *)childController;
    }

}

This will save your from iterating trough the childViewControllers.

Alexander Ney
  • 771
  • 3
  • 10