Miss Lucy Miss Lucy - 6 months ago 28
Objective-C Question

Objective C - how to add a subview that has its own UIViewController

I am struggling with subviews that have their own

UIViewControllers
. I have a
UIViewController
with a view (light pink) and two buttons on a
toolbar
. I want blue view to display when the first button is pressed and the yellow view to display with the second button is pressed. Should be easy if I just wanted to display a view. But the blue view will contain a table, so it needs it's own controller. That was my first lesson. I started off with this SO question where I learned I needed a controller for the table.

So, I am going to back up and take some baby steps here. Below is a picture of a simple starting point with my Utility
ViewController
(the main view controller) and the other two controllers (blue and yellow). Imagine that when the Utility
ViewController
(the main view) is first displayed the blue (default) view will be displayed where the pink view is located. Users will be able to click the two buttons to go back and forth and the pink view will NEVER be displayed. I just want the blue view to go where the pink view is and the yellow view to go where the pink view is. I hope this makes sense.

Simple Storyboard image

I'm trying to use
addChildViewController
. From what I have seen, there are two ways to do this: The Container View in the
storyboard
or
addChildViewController
programmatically. I want to do it programmatically. I don't want to use a
NavigationController
or a Tab bar. I just want to add the controllers and shove the correct view into the pink view when the associated button is pressed.

Below is the code I have so far. All I want to do is display the blue view where the pink view is. From what I have seen I should be able to just
addChildViewController
and addSubView. This code is not doing that for me. My confusion is getting the better of me. Can somebody help me get the blue view displayed where the pink view is?

This code is not intended to do anything other than display the blue view in viewDidLoad.

IDUtilityViewController.h

#import <UIKit/UIKit.h>

@interface IDUtilityViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *utilityView;
@end


IDUtilityViewController.m

#import "IDUtilityViewController.h"
#import "IDAboutViewController.h"

@interface IDUtilityViewController ()
@property (nonatomic, strong) IDAboutViewController *aboutVC;
@end

@implementation IDUtilityViewController

- (void)viewDidLoad
{
[super viewDidLoad];

self.aboutVC = [[IDAboutViewController alloc]initWithNibName:@"AboutVC" bundle:nil];
[self addChildViewController:self.aboutVC];
[self.aboutVC didMoveToParentViewController:self];
[self.utilityView addSubview:self.aboutVC.aboutView];
}

@end


--------------------------EDIT------------------------------

The self.aboutVC.aboutView is nil. But I wired it up in the
storyboard
. Do I still need to instantiate it?

enter image description here

Answer

In iOS today "everything is a container view". It is the basic way you make apps today.

An app may be so simple that it is just the one view.

But even in that case, when you have "different things on the screen", then each of those "things" is a container view.

It's this simple...


(A) Drag a container view in to your scene...

The container view is the brown thing in this image. It is actually INSIDE YOUR SCENE VIEW.

enter image description here

When you drag a container view into your scene view, Xcode automatically gives you two things:

  1. You get the container view inside your scene view, and,

  2. you get a new UIViewController which is just sitting around somewhere on the white of the storyboard.

The two are connected with the "Masonic Symbol" thing. Explained below.


(B) Click on that new view controller (the new thing Xcode made for you somewhere on the white area, not the thing inside your scene) ... and, change the class!

It's really that simple.

You're done.

Here's the same thing explained visually:

Notice the container view at (A) in this image...

enter image description here

Now notice the controller at (B).

Click on B. (Not A!) Then go to the inspector at the top right...

enter image description here

Change it to your own custom class, which is a UIViewController.

So, I have a .h file like this: UltraList.h

@interface UltraList:UIViewController

As you can see, UltraList is a particular type of view controller. So in the example I would click on (B) in the image above. (To repeat!!!! that's (B), not (A).) Then change the class to UltraList. (As soon as you start typing "ultr ..." it will guess UltraList, as usual in Xcode.)

Advanced! Don't forget, Apple gives you a UIViewController by default. This is silly: it should ask which type you need. Often you want some other controller - for example, very often you want a collection view. If you want a different type of controller, here is how to do it:

At the time of writing, Xcode gives you a UIViewController by default. Let's say you want a UICollectionViewController instead. Do this:

(i) Drag a UICollectionViewController to anywhere on the main white area of the screen.

(ii) Click on the unwanted UIViewController which Apple gave you by default; now click on connections and notice the viewDidLoad-embed segue.

iii) Simply delete the unwanted UIViewController.

iv) On your new UICollectionViewController, drag the viewDidLoad-embed to the container view; select 'segue', which is the only choice. Done!!

Short version: delete the unwanted UIViewController. Put a new say UITableViewController or UICollectionViewController on the storyboard. Control-drag from the container view to your new controller.

.

Next -- note that you should and must have one of these "square in a square" Masonic symbol things: it is on the "bendy line" between your main scene view and the new (B) view.

enter image description here

Click on it. That's exactly where you type in a text identifier for the segue.


IMPORTANT - CRITICAL you now have the problem of how to get at these sub- view controllers, in a property, in your main controller.

Interestingly you CAN NOT do this in storyboard (as of 2014): there's nothing you can "drag" to hook that up. You have to do it in code in prepareForSegue. Fortunately it is very easy.

override func prepareForSegue(segue:(UIStoryboardSegue!), sender:AnyObject!)
    {
    if (segue.identifier == "feedContainer")
        {
        feed = segue!.destinationViewController as! Feed
        feed.someFunction()
        }
    }

Tip: when you're "in" the (for example) collection view, it can be a little confusing to "get to" the actual scene view controller.

Fortunately, this is as simple as saying:

YourScene *parent = (YourScene *)self.parentViewController;

Here is a good full example of that, in relation to how you might "peel off" a cell from a collection view, and then have the actual view controller ("the thing with the container view") deal with it. It's a good example because typically the collection view (not the "boss" view controller) would get that message about someone having clicked on something in a cell or on the whole cell.

http://stackoverflow.com/a/24339705/294884


Here's a quick rant on the very confusing relationship between "actual segues" and container views. Confusingly, apple wrapped these things up in the same package of code. Here's some explanation of that ... http://stackoverflow.com/a/24351813/294884

Hope it helps someone.


Finally, I've found there's a common situation where, you have an area on the screen, and, you want to have one of a number of view controllers shown there. So, simply you have a box, and there are four possible things you want to display there (say, Parts, Tires, Brakes, Oils). it will only be one or the other, and they won't change.

Nowadays you can do that properly using container views.

But, horribly Apple has not yet (2014) allowed for multiple views in the same container, which is way silly because it's normal you'd have a choice of things to show in the same area.

You can certainly do it by just having a number of container views all sitting in the identical position. (Just hide the unused ones.)

Fortunately there is a simple old-fashioned solution, still "somewhat" using the storyboard.

(1) Just make a UIView (self.holder) that is the shape/position you want. (2) Create the four view controllers in question, Parts, Tires, Brakes, Oils. (3) Give each one an ID such as "TiresID". (4) Simply sit the four on the storyboard nearby - in fact don't connect with any segues to self.holder. (5) Then just do this to load one or the other of those VC...

-(void)putTiresInThatArea
    {
    // your four view controllers have IDs such as "TiresID"
    Tires *tires = [self.storyboard
        instantiateViewControllerWithIdentifier:@"TiresID"];
    dossier.view.frame = self.holer.bounds;

    [self addChildViewController:tires];
    [self.holder addSubview:tires.view];
    [tires didMoveToParentViewController:self];
    }

Note that, indeed, since Apple annoyingly does not yet offer the concept "more than one VC in the same container", there are a number of discussions around regarding this issue. Eg: article , question Note that UIPageViewController is sometimes relevant, for "a number of views in the same area".


Note - Storyboard References arrive!

As SimplGy points out below "iOS 9's Storyboard References make container views even more awesome. You can define your reusable view (controller) wherever you like and reference it from any container view in multiple, modular storyboards."

Comments