How to Properly Add Child View Controller in iOS 8 with Swift

How To Properly Add Child View Controller in iOS 8 With Swift

If you want the red controller to be the child controller, delete the yellow one, and control-drag from the container to the red controller. There's no need to add it in code, or do any resizing. The red controller will be set to the same size as the container in the storyboard.

Add child view controller swift

When I use your code, run the app, and pause the app, and look at the view hierarchy, I see the following:

(lldb) po [[UIWindow keyWindow] recursiveDescription]
<UIWindow: 0x156bdc30; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x156be750>; layer = <UIWindowLayer: 0x156aa3c0>>
| <UIView: 0x156c5440; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x156c55c0>>
| | <UIScrollView: 0x156c2740; frame = (0 0; 0 568); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x156c4d00>; layer = <CALayer: 0x156c2a80>; contentOffset: {0, 0}; contentSize: {960, 568}>
| | | <UIView: 0x156c6df0; frame = (640 0; 0 536); autoresize = W+H; layer = <CALayer: 0x156c6d80>>
| | | <UIView: 0x156c7100; frame = (320 0; 0 536); autoresize = W+H; layer = <CALayer: 0x156c70a0>>
| | | <UIView: 0x156c73f0; frame = (0 0; 0 536); autoresize = W+H; layer = <CALayer: 0x156c7390>>
| | | <UIImageView: 0x156c8bd0; frame = (0 564.5; 600 3.5); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x156c8c50>>
| | | <UIImageView: 0x156c9020; frame = (-3.5 32; 3.5 568); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x156c90a0>>
| | <_UILayoutGuide: 0x156c5620; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x156c5800>>
| | <_UILayoutGuide: 0x156c5c90; frame = (0 568; 0 0); hidden = YES; layer = <CALayer: 0x156c5d10>>

If you're not seeing your subviews there, then likely culprits include:

  • You may not have specified the view controller base class in your scene, and thus this code isn't being run. You can confirm this with a println log statement or breakpoint in side this viewDidLoad and make sure you're hitting this routine.

  • You may not have hooked up the @IBOutlet for the scrollView, and thus scrollView is nil. Confirm this by putting breakpoint in viewDidLoad and examining the scrollView property.


In your revised question, we can now see that the three subviews are present and appear to be the correct size. That's great.

Now the question is why you don't see anything. If you have these defined as scenes in your storyboard, you should:

  • Make sure the "base class" and the "storyboard identifier" is defined for each of these three child scenes; and

  • When your main view controller instantiates the three child view controllers, you would instantiate them from the storyboard using the storyboard identifier (in my example, I used storyboard identifiers of A, B, and C, respectively):

    let Avc = storyboard.instantiateViewControllerWithIdentifier("A") as AViewController
    let Bvc = storyboard.instantiateViewControllerWithIdentifier("B") as BViewController
    let Cvc = storyboard.instantiateViewControllerWithIdentifier("C") as CViewController

If you do the above and repeat the recursiveDescription you should see your scene's subviews (e.g. the labels you added) appear in the output.

iOS : Why do we need to add child view controller when adding view as subview does the work?

Certainly viewDidLoad was called. That happened instantly as soon as you referred to ViewController2's view in your code.

But let's say your ViewController2 does other things besides load a view. Suppose its view contains a button that is hooked through an action to a function in ViewController2. If you now tap that button nothing happens.

That's because the ViewController2 itself is dead: it has vanished in a puff of smoke.

You can see that by implementing deinit in ViewController2. You will see that, just as viewDidLoad is called, so is deinit. You are left with a view controller's view that has no view controller. That is bad.

There is a view controller hierarchy that is responsible for maintaining relations between view controllers. When you add ViewController2 as a child view controller of ViewController1, you maintain that hierarchy, and you maintain it correctly according to the rules, which say:

If VC2's view is somewhere inside VC1's view, then VC2 needs to be a child (at some depth) of VC1.

In other words, the view hierarchy and the view controller hierarchy must run together. Otherwise, the responder chain will break and life will become chaos.


(There are other requirements when you make one view controller the child of another, like sending didMoveToParent to the child as part of the opening dance, along with other message forwarding responsibilities later, so as to ensure that the child view controller gets other messages like viewDidAppear at the right time. It's a complex business. However, I've focussed my answer on the very basic part of what you asked.)


I should add: if your goal was merely to fetch a view out of a nib and stuff it into your own view, you can certainly do that, no problem. What you must not do is use a view controller as a kind of magnet or vacuum cleaner to fetch a view for you if your intention is then to let go of the view controller itself.

Add child view controller to current view controller

didMoveToParentViewController must be the last.

How to add Child View Controller to TableViewController

It's not prefereed nor right to add child vcs to a tableController , as the content will scroll with the parent table as view = tableView inside tableController it could work but it needs some a tweak inside scrollViewDidScroll , so it's better to add it to a regular vc and add the table as a property

How to properly access a child view controller

Since you mention a "container view" I am assuming you invoke the child view controller using an embed segue.

Assuming that's the case you should implement a prepare(for:sender) method that saves a pointer to the child:

Give the parent view controller an instance variable:

var childView: ChildView?

And then in your prepare(for:sender) method

func prepare(for segue: NSStoryboardSegue, sender: Any?) {
if let dest = segue.destination as? ChildView {
childView = dest
}
}

Then when you need to send messages to the childView:

childView?.runFunction()

(Method names should start with lower-case letters)

Child View Controller and Margin

You don't give the child view neither a frame nor constraints

child.view.translatesAutoresizingMaskIntoConstraints = false 

Also you should add view not imageView

view.addSubview(child.view) 
NSLayoutConstraint.activate([
child.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
child.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 8),
child.view.leftAnchor.constraint(equalTo: view.leftAnchor),
child.view.rightAnchor.constraint(equalTo: view.rightAnchor),
])

and set

navigationController?.navigationBar.prefersLargeTitles = false

topLayoutGuide in child view controller

While this answer might be correct, I still found myself having to travel the containment tree up to find the right parent view controller and get what you describe as the "real topLayoutGuide". This way I can manually implement automaticallyAdjustsScrollViewInsets.

This is how I'm doing it:

In my table view controller (a subclass of UIViewController actually), I have this:

- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];

_tableView.frame = self.view.bounds;

const UIEdgeInsets insets = (self.automaticallyAdjustsScrollViewInsets) ? UIEdgeInsetsMake(self.ms_navigationBarTopLayoutGuide.length,
0.0,
self.ms_navigationBarBottomLayoutGuide.length,
0.0) : UIEdgeInsetsZero;
_tableView.contentInset = _tableView.scrollIndicatorInsets = insets;
}

Notice the category methods in UIViewController, this is how I implemented them:

@implementation UIViewController (MSLayoutSupport)

- (id<UILayoutSupport>)ms_navigationBarTopLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarTopLayoutGuide;
} else {
return self.topLayoutGuide;
}
}

- (id<UILayoutSupport>)ms_navigationBarBottomLayoutGuide {
if (self.parentViewController &&
![self.parentViewController isKindOfClass:UINavigationController.class]) {
return self.parentViewController.ms_navigationBarBottomLayoutGuide;
} else {
return self.bottomLayoutGuide;
}
}

@end

Hope this helps :)



Related Topics



Leave a reply



Submit