What does addChildViewController actually do?
I was wondering about this question too. I watched Session 102 of the WWDC 2011 videos and Mr. View Controller, Bruce D. Nilo, said this:
viewWillAppear:
,viewDidAppear:
, etc have nothing to do withaddChildViewController:
. All thataddChildViewController:
does is to say "This view controller is a child of that one" and it has nothing to do with view appearance. When they get called is associated with when views move in and out of the window hierarchy.
So it seems that the call to addChildViewController:
does very little. The side effects of the call are the important part. They come from the parentViewController
and childViewControllers
relationships. Here are some of the side effects that I know:
- Forwarding appearance methods to child view controllers
- Forwarding rotation methods
- (Possibly) forwarding memory warnings
- Avoiding inconsistent VC hierarchies, especially in
transitionFromViewController:toViewController:…
where both VCs need to have the same parent - Allowing custom container view controllers to take part in State Preservation and Restoration
- Taking part in the responder chain
- Hooking up the
navigationController
,tabBarController
, etc properties
To call addChildViewController or not to call addChildViewController
One of the major design assumptions in UIKit is that the ViewController
hierarchy will generally be in sync with the View
hierarchy. Callbacks handling autorotation and size class transitions are passed down through the ViewController
chain, and if you never create the parent-child relationship between ViewController
s this system breaks down.
I follow the pattern of (psudo code)
//given:
ViewController *a;
ViewController *b;
[a willMoveToViewController:b];
[b addChildViewController:a];
[a didMoveToViewController:b];
[b.view addSubview:a.view];
Difference between addChildViewController and addSubview?
They are very different. addChildViewController associates a view controller with a parent container view controller, while addSubview adds a view to the view hierarchy of the view it is being added to. In the former case, the new child view controller will be responsible for handling events when it is the selected view controller of its parent. Think of a tab bar controller--each tab has its own associated "child" view controller that displays its view within the parent tab bar controller's content area and handles any user interaction within that view when its corresponding tab is selected in the tab bar. You should only use addChildViewController when you have a custom container view and want to add a new view controller to its childViewControllers property. If you just want to add a new view to the view hierarchy that can receive events, which is what it kind of sounds like, addSubview is the way to go. "Implementing a Container View Controller" section explains what addChildViewController is for.
When to use addChildViewController vs pushViewController
Yes, pushViewController:
is for navigation controllers that manage a stack of view controllers. addChildViewController:
on the other hand is part of an iOS 5 feature called "view controller containment".
The basic idea behind this is that you can embed your view controllers into other view controllers of your own (e.g. when porting an iPhone app to the iPad) and thus easily do your own implementation of things like navigation controllers, split view controllers etc.
One problem with an implementation like the one you show is that you only handle views. View controller events like orientation changes will not be passed properly down the hierarchy. View controller containment tries to ensure that all contained view controllers will get the appropriate messages, too.
Looking at your implementation you should also think about what you really want to achieve by this. A navigation controller may be the right thing or you might even show the next controller modally (see https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/PresentingaViewController.html) A bonus when using these methods (e.g. navigation controllers and modal views) is that the user is already familiar with those navigation techniques.
In any case https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ is a good read about how to transition between view controllers.
When using view controller containment you basically have to add the view to the containing view as usual (this has to be done even if the controller is added). Then you use addChildViewController:
to add the child view controller to the surrounding one. You also have to notify the child controller by didMoveToParentViewController:
that it has been put into another controller. You can also use transitionFromViewController:toViewController:
to exchange one view controller for another, optionally giving an animation.
How does View Controller Containment work in iOS 5?
The UIViewController
docs are pretty clear on when and when not to call willMove
/didMove
methods. Check out the "Implementing a Container View Controller" documentation.
The docs say, that if you do not override addChildViewController
, you do not have to call willMoveToParentViewController:
method. However you do need to call the didMoveToParentViewController:
method after the transition is complete. "Likewise, it is is the responsibility of the container view controller to call the willMoveToParentViewController:
method before calling the removeFromParentViewController
method. The removeFromParentViewController
method calls the didMoveToParentViewController:
method of the child view controller."
Also, there is an example worked out here and sample code here.
Good Luck
When to call addChildViewController
I don't know if this is what's causing your problem, but shouldn't this:
[self didMoveToParentViewController:toViewController];
be:
[toViewController didMoveToParentViewController:self];
Also, I'm not sure what you're doing with the subViewControllers array. It seems to be a duplication of the childViewControllers array that is already a property of a UIViewController.
One other thing I'm not sure is right. In your pop method your toViewController is the last controller in the _subViewControllers array. Don't you want it to be the second to last? Shouldn't the last be the one you're popping? You're popping vc, which is a controller you're passing in to the method, I don't understand that.
This is the way I've made a navigation like controller. In its containment behavior, it acts like a navigation controller, but without a navigation bar, and allows for different transition animations:
@implementation ViewController
-(id)initWithRootViewController:(UIViewController *) rootVC {
if (self = [super init]) {
[self addChildViewController:rootVC];
rootVC.view.frame = self.view.bounds;
[self.view addSubview:rootVC.view];
}
return self;
}
-(void)pushViewController:(UIViewController *) vc animation:(UIViewAnimationOptions)animation {
vc.view.frame = self.view.bounds;
[self addChildViewController:vc];
[self transitionFromViewController:self.childViewControllers[self.childViewControllers.count -2] toViewController:vc duration:1 options:animation animations:nil
completion:^(BOOL finished) {
[vc didMoveToParentViewController:self];
NSLog(@"%@",self.childViewControllers);
}];
}
-(void)popViewControllerAnimation:(UIViewAnimationOptions)animation {
[self transitionFromViewController:self.childViewControllers.lastObject toViewController:self.childViewControllers[self.childViewControllers.count -2] duration:1 options:animation animations:nil
completion:^(BOOL finished) {
[self.childViewControllers.lastObject removeFromParentViewController];
NSLog(@"%@",self.childViewControllers);
}];
}
-(void)popToRootControllerAnimation:(UIViewAnimationOptions)animation {
[self transitionFromViewController:self.childViewControllers.lastObject toViewController:self.childViewControllers[0] duration:1 options:animation animations:nil
completion:^(BOOL finished) {
for (int i = self.childViewControllers.count -1; i>0; i--) {
[self.childViewControllers[i] removeFromParentViewController];
}
NSLog(@"%@",self.childViewControllers);
}];
}
After Edit: I was able to duplicate the back button function with this controller by adding a navigation bar to all my controllers in IB (including in the one that is the custom container controller). I added a bar button to any controllers that will be pushed, and set their titles to nil (I got some glitches if I left the title as "item"). Deleting that title makes the button disappear (in IB) but you can still make connections to it in the scene list. I added an IBOutlet to it, and added this code to get the function I wanted:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.isMovingToParentViewController) {
self.backButton.title = [self.parentViewController.childViewControllers[self.parentViewController.childViewControllers.count -2] navigationItem].title;
}else{
self.backButton.title = [self.parentViewController.childViewControllers[self.parentViewController.childViewControllers.count -3] title];
}
}
I've shown two different ways that worked to access a title -- in IB you can set a title for the controller which I used in the else clause, or you can use the navigationItem title as I did in the if part of the clause. The "-3" in the else clause is necessary because at the time viewWillAppear is called, the controller that is being popped is still in the childViewControllers array.
Switch Between Child View Controllers
You can do it using protocol
protocol StartVcProtocol {
func startButtonPressed()
}
Let HomeViewController implement it
extension HomeViewController: StartVcProtocol {
func startButtonPressed() {
// start button pressed -- do your remove and add stuff here
}
}
Now in StartVc
class StartVc: UIViewController {
var delegate: StartVcProtocol?
//inside you start button iBaction
delegate?.startButtonPressed()
}
Then when lazy initializing the StartVc
viewController.delegate = self
Hope you get all the pieces .
Why do I need to call addSubview if i'm immediately following it with addChildViewController
Your main concern as I see is calling addSubview
seems redundant?
I would say no it is not redundant, coz addChildViewController
just Adds the given view controller as a child but it will not load/add the view
.
This makes it essential to call addSubview
addSubview
Add the child controller’s view as a subview.
For more info see:
Creating Custom Content View Controllers
Implementing a Custom Container View Controller
Related Topics
Which iOS App Version/Build Number(S) Must Be Incremented Upon App Store Release
How to I Import 3Rd Party Frameworks into Xcode Playground
How to Access My Viewcontroller from My Appdelegate? iOS
Cannot Assign a Value of Type "String" to Type "Uilabel" in Swift
How to Get Touches When Parent View Has Userinteractionenabled Set to No in iOS
How to Remove HTML Tags from Nsstring in Iphone
Missing Private Key in the Distribution Certificate on Keychain
Keeping a Uibutton Selected After a Touch
How to Access Coredata Model in Today Extension (Ios)
Change Text Color of Items in Uiactionsheet - iOS 8
Building for iOS Simulator, But the Linked Framework '****.Framework' Was Built for iOS
Differencebetween Pan and Swipe in iOS
Didreceiveremotenotification Not Working in the Background
When Should I Use the Various Storage Mechanisms in iOS