What does addChildViewController actually do? - ios

I'm just dipping my feet for the first time into iOS development, and one of the first things I've had to do is implement a custom container view controller - lets call it SideBarViewController - that swaps out which of several possible child view controllers it shows, almost exactly like a standard Tab Bar Controller. (It's pretty much a Tab Bar Controller but with a hideable side menu instead of a tab bar.)
As per the instructions in the Apple documentation, I call addChildViewController whenever I add a child ViewController to my container. My code for swapping out the current child view controller being shown by the SideBarViewController looks like this:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers
objectAtIndex:0];
[oldViewController removeFromParentViewController];
[oldViewController.view removeFromSuperview];
newViewController.view.frame = CGRectMake(
0, 0, self.view.frame.size.width, self.view.frame.size.height
);
[self addChildViewController: newViewController];
[self.view addSubview: newViewController.view];
}
Then I started trying to figure out just what addChildViewController does here, and I realised that I have no idea. Besides sticking the new ViewController in the .childViewControllers array, it seems to have no effect on anything. Actions and outlets from the child controller's view to the child controller that I've set on the storyboard still work just fine even if I never call addChildViewController, and I can't imagine what else it could affect.
Indeed, if I rewrite my code to not call addChildViewController, and instead look like this...
- (void)showViewController:(UIViewController *)newViewController {
// Get the current child from a member variable of `SideBarViewController`
UIViewController* oldViewController = currentChildViewController;
[oldViewController.view removeFromSuperview];
newViewController.view.frame = CGRectMake(
0, 0, self.view.frame.size.width, self.view.frame.size.height
);
[self.view addSubview: newViewController.view];
currentChildViewController = newViewController;
}
... then my app still works perfectly, so far as I can tell!
The Apple documentation doesn't shed much light on what addChildViewController does, or why we're supposed to call it. The entire extent of the relevant description of what the method does or why it should be used in its section in the UIViewController Class Reference is, at present:
Adds the given view controller as a child.
...
This method is only intended to be called by an implementation of a custom container view controller. If you override this method, you must call super in your implementation.
There's also this paragraph earlier on the same page:
Your container view controller must associate a child view controller with itself before adding the child’s root view to the view hierarchy. This allows iOS to properly route events to child view controllers and the views those controllers manage. Likewise, after it removes a child’s root view from its view hierarchy, it should disconnect that child view controller from itself. To make or break these associations, your container calls specific methods defined by the base class. These methods are not intended to be called by clients of your container class; they are to be used only by your container’s implementation to provide the expected containment behavior.
Here are the essential methods you might need to call:
addChildViewController:
removeFromParentViewController
willMoveToParentViewController:
didMoveToParentViewController:
but it doesn't offer any clue as to what the 'events' or 'expected containment behavior' that it's talking about are, or why (or even when) calling these methods is 'essential'.
The examples of custom container view controllers in the "Custom Container View Controllers" section of the Apple documentation all call this method, so I assume that it serves some important purpose beyond just popping the child ViewController onto an array, but I can't figure out what that purpose is. What does this method do, and why should I call it?

I think an example is worth a thousand words.
I was working on a library app and wanted to show a nice notepad view that appears when the user wants to add a note.
After trying some solutions, I ended up inventing my own custom solution to show the notepad. So when I want to show the notepad, I create a new instance of NotepadViewController and add its root view as a subview to the main view. So far so good.
Then I noticed that the notepad image is partially hidden under the keyboard in landscape mode.
So I wanted to change the notepad image and shift it up. And to do so, I wrote the proper code in willAnimateRotationToInterfaceOrientation:duration: method, but when I ran the app nothing happened! And after debugging I noticed that none of UIViewController's rotation methods is actually called in NotepadViewController. Only those methods in the main view controller are being called.
To solve this, I needed to call all the methods from NotepadViewController manually when they're called in the main view controller. This will soon make things complicated and create an extra dependency between unrelated components in the app.
That was in the past, before the concept of child view controllers is introduced. But now, you only need to addChildViewController to the main view controller and everything will just work as expected without any more manual work.
Edit:
There are two categories of events that are forwarded to child view controllers:
1- Appearance Methods:
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
2- Rotation Methods:
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
You can also control what event categories you want to be forwarded automatically by overriding shouldAutomaticallyForwardRotationMethods and shouldAutomaticallyForwardAppearanceMethods.

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 with addChildViewController:. All that addChildViewController: 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

-[UIViewController addChildViewController:] only adds the passed in view controller in an array of viewControllers that a viewController (the parent) wants to keep reference of. You should actually add those viewController's views on screen yourself by adding them as a subviews of another view (e.g. the parentViewController's view). There's also a convenience object in Interface Builder to use childrenViewControllers in Storyboards.
Previously, to keep reference of other viewControllers of which you used the views of, you had to keep manual reference of them in #properties. Having a build-in property like childViewControllers and consequently parentViewController is a convenient way to manage such interactions and build composed viewControllers like the UISplitViewController that you find on iPad apps.
Moreover, childrenViewControllers also automatically receive all the system events that the parent receives: -viewWillAppear, -viewWillDisappear, etc. Previously you should have called this methods manually on your "childrenViewControllers".
That's it.

What does addChildViewController actually do?
It is the first step of view containment, a process by which we keep the view hierarchy in sync with the view controller hierarchy, for those cases where we have a subview that has encapsulated its logic in its own view controller (to simplify the parent view controller, to enable a reusable child view with its own logic, etc.).
So, addChildViewController adds the child view controller to an array of childViewControllers, which keeps track of the children, facilitates them getting all the view events, keeps a strong reference to the child for you, etc.
But note, addChildViewController is only the first step. You also have to call didMoveToParentViewController, too:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers objectAtIndex:0];
[oldViewController willMoveToParentViewController:nil]; // tell it you are going to remove the child
[oldViewController.view removeFromSuperview]; // remove view
[oldViewController removeFromParentViewController]; // tell it you have removed child; this calls `didMoveToParentViewController` for you
newViewController.view.frame = self.view.bounds; // use `bounds`, not `frame`
newViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // be explicit regarding resizing mask if setting `frame`
[self addChildViewController:newViewController]; // tell it that you are going to add a child; this calls `willMoveToParentViewController` for you
[self.view addSubview:newViewController.view]; // add the view
[newViewController didMoveToParentViewController:self]; // tell it that you are done
}
As an aside, please note the sequence of calls, in which the order that you call these is important. When adding, for example, you call addChildViewController, addSubview, and didMoveToParentViewController, in that order. Note, as the documentation for didMoveToParentViewController says:
If you are implementing your own container view controller, it must call the didMoveToParentViewController: method of the child view controller after the transition to the new controller is complete or, if there is no transition, immediately after calling the addChildViewController: method.
And, if you are wondering why we don't call willMoveToParentViewController in this case, too, it is because, as the documentation says, addChildViewController does that for you:
When your custom container calls the addChildViewController: method, it automatically calls the willMoveToParentViewController: method of the view controller to be added as a child before adding it.
Likewise, when removing, you call willMoveToParentViewController, removeFromSuperview, and removeFromParentViewController. As the documentation for willMoveToParentViewController says:
If you are implementing your own container view controller, it must call the willMoveToParentViewController: method of the child view controller before calling the removeFromParentViewController method, passing in a parent value of nil.
And, again, if you are wondering why we don't call didMoveToParentViewController when removing the child, that is because, as the documentation says, removeFromParentViewController does that for you:
The removeFromParentViewController method automatically calls the didMoveToParentViewController: method of the child view controller after it removes the child.
FYI, if animating the removal of the subview, put the call to removeFromParentViewController in the animation completion handler.
But if you perform the correct sequence of containment calls, outlined above, then the child will receive all of the appropriate view-related events.
For more information (in particular, why these willMoveToParentViewController and didMoveToParentViewController calls are so important), see WWDC 2011 video Implementing UIViewController Containment. Also see the Implementing a Container View Controller section of the UIViewController documentation.
As a minor observation, make sure that when you are adding the child’s view as a subview, reference the bounds of the parent view controller’s view, not the frame. The frame of the parent’s view is in the coordinate system of its superview. The bounds is in its own coordinate system.
You might not notice the difference when the parent view occupies the full screen, but as soon as you employ this in a scenario where the parent’s view doesn't happen to take up the full screen, you will start to encounter frame misalignment. Always use bounds when setting up coordinates for children. (Or use constraints, which gets you out of that silliness, altogether.)
Perhaps needless to say, if you want to just add the child when the parent is instantiated, one can do view controller containment entirely in storyboards without any of these add/remove and willMove/didMove calls at all. Just use “Container View” and pass whatever data is needed by the child during initialization using prepareForSegue.
For example, if the parent had a property called bar and you wanted to update a property called baz in the child:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[ChildViewController class]]) {
ChildViewController *destination = segue.destinationViewController;
destination.baz = self.bar;
}
}
Now, if you want to programmatically add/remove children, then use as outlined above. But storyboard “Container View” can handle all the view containment calls for simple scenarios with very little code.

Related

View Controller Containment when using Interface Builder

I'm attempting define a series of container view controllers using Interface Builder (probably my first mistake). In the storyboard, I created the top view controller, added 3 container views, which automatically added each child view controller to the storyboard. I added an outlet for each container view, and am able to successfully maneuver through the child views by hiding/showing the container views. There's really not much code to speak of, it's just:
-(IBAction) button1Pushed:(id)sender
{
containerView2.hidden = true;
containerView1.hidden = false;
}
While that works, I need to update the content on each showing. viewWillAppear (and related functions) fire for the Child View Controllers only on the initial creation, not when hiding/showing the containers. I supposed I could add something like:
[childVC1 updateContent];
containerView1.hidden = false;
but I was hoping I could rely on viewWillAppear (and related functions). I've tried a few things with limited success, and have several questions:
I understand the example in Apple's Programming Guide for manually creating containers:
[self addChildViewController:content]; // 1
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self]; // 3
but how does that apply when using IB? There's no mention of IB in that Programming Guide. IB must be calling addChildViewController, because I can find all the the container VCs using [self childViewControllers]. But since viewWillAppear only happens on creation, does that mean IB didn't go on to add the views as well?
So question 1 is: when adding container views via Interface Builder, how much of the example code does IB handle and how much do I have to implement?
As an experiment, I added this code in the parent's viewDidLoad:
for ( UIViewController *vc in [self childViewControllers])
{
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
}
That caused each of the child views to all appear at once on top of each other at the top left of the screen. It no longer respects the IB layout, and unsurprisingly, no longer respects showing/hiding the containerView, either. I can control them with
vc.view.hidden = true;
but they're moved to some default position. I could manually reset their coordinates, but one of the reasons for using IB in the first place was to avoid doing screen positioning via code.
question #2 is: when adding container views in IB, how am I supposed to manage displaying the children while still honoring the IB layout? By manipulating the container view's outlet? by manipulating the child view controller (found via [self childViewControllers])? Or something else?
At the end of the day, my desired state is:
Define the screen layouts and positions in IB for the parent and children
Hide/show children based on buttons selected by the user
viewWillAppear fires each time a child is displayed, allowing me to update the content
Any advice on reaching this state is appreciated, thanks!
Answer 1: When you add child view controllers via the storyboard (called container views in this case) then the children are added via segues. No it's not a segue in the normal sense where you push or present a view controller, but the method that's called in your 'super' view controller is prepareForSegue. In interface builder you can name the connection between the two controllers just like a normal segue and grab a reference to the view controller in this instance. This takes the places of the whole dance of addChildViewController, didMoveToParent and so forth.
Answer two: I think it makes sense to address your desired state. I looks like you have already solved #1 and #2. As for getting viewWillAppear to fire again you would have to get your parents viewWillAppear to fire again by doing something like pushing and popping a new VC or presenting and dismissing one. Just because you set it to hidden and then unhide it won't make it fire again (hidden basically tells the drawing system to not render it).
The recommended approach (from Apple, forgive me I don't know the link) is to do all of your updates in your view subclass via updateViewContraints or layoutSubviews. In the view controller world the similar method is ViewDidLayoutSubviews. You can signal to your view that it needs to be laid out again by calling [yourViewController.view setsNeedsLayout]. For reference check out the doc on UIView in regards to needsLayout.
Likely what you'll want to do when your button is pressed to unhide your viewController is something like:
1: set any properties or call methods on your viewController which update its content
2: call setsNeedsLayout on your viewControllers view
3: unhide it.
viewWillAppear will not fire for the children each time they are shown. Adjust your thinking.
When you create a container view and a child view in IB, it sets up an embed segue to link to the child view controllers.
What I suggest you do is to add unique segue identifiers for each of your child view controllers, and then in prepareForSegue, use an if/else if/else if statement to match each segue ID. When you find a segue Id, save that child view controller in a property of the parent:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString: #"firstChildID")
self.firstChildVC = (FirstChildVC *) segue.destinationViewController;
else if ([segue.identifier isEqualToString: #"secondChildID")
self.secondChildVC = (SecondChildVC *) segue.destinationViewController;
else if ([segue.identifier isEqualToString: #"thirdChildID")
self.thirdChildVC = (ThirdChildVC *) segue.destinationViewController;
}
Then define a protocol that you use to communicate with your child view controllers to tell them that they are being shown:
#protocol ChildVCProtocol
- (void) getReadyToShowView;
//whatever other methods
#end
Then make each of your child VCs conform to that protocol. Put your code to get ready to be shown in the getReadyToShowView method. Call that method every time you make a child VC visible.

ARC ViewController gets deallocated

I have a view controller that displays a button. When I click the button, the corresponding selector needs to be called. However, with ARC, the application crashed with an EXC_BAD_ACCESS message.
-(IBAction)reseauPushed:(id)sender{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self.viewCenter addSubview:self.reseauVC.view];
}
with
#property (strong, nonatomic) ReseauVCIpad *reseauVC;
and the crash log :
-[ReseauVCIpad performSelector:withObject:withObject:]: message sent to deallocated instance
I use ARC.
The button action :
-(IBAction)helloPushed:(id)sender{
NSLog("hello);
}
This ReseauVCIpad view controller is obviously getting deallocated some how. Either you accidentally are setting self.reseauVC to nil somewhere, or, more likely, the parent view controller, itself, is somehow getting deallocated. Is there any chance you did this addSubview technique for any of the preceding view controllers? And if not, how did you instantiate the root view controller?
To diagnose where the problem is, I'd suggest you add dealloc methods to all of your various view controllers so that you can confirm if any are getting deallocated prematurely. (Either set breakpoints or put in NSLog statements.) I'd wager you're seeing the parent of ReseauVCIpad getting deallocated, which is, in turn, allowing ReseauVCIpad itself to be deallocated.
By the way, as others have pointed out, the typical answer to this problem is to make sure you're doing the appropriate containment calls (show below), or if this child view controller takes up the whole screen, you should just be pushing to it or modally presenting it. Clearly, you have a strong reference to ReseauVCIpad, so the lack of containment calls isn't the source of the problem with ReseauVCIpad itself (though I wonder if you are doing this addSubview trick without containment calls with one or more of ReseauVCIpad's parent view controllers).
But you still should be doing these containment calls (or do a proper modal/push transition), regardless, to ensure your view controller hierarchy stays in sync with your view hierarchy (see WWDC 2011 video Implementing UIViewController Containment for lengthy discussion of why this is important). The appropriate containment calls for adding a subview with its own controller is, at a minimum, as follows:
- (IBAction)reseauPushed:(id)sender
{
self.reseauVC = [[ReseauVCIpad alloc]initWithNibName:#"ReseauVCIpad" bundle:nil];
[self addChildViewController:self.reseauVC];
[self.viewCenter addSubview:self.reseauVC.view];
[self.reseauVC didMoveToParentViewController:self];
}
For a more detailed description, see the video I referenced above, or see the Creating Custom Container View Controllers section of the View Controller Programming Guide for iOS.
And when you want to remove it, you should do the appropriate containment calls there, too:
- (void)removeReseau
{
[self.reseauVC willMoveToParentViewController:nil];
[self.reseauVC.view removeFromSuperview];
[self.reseauVC removeFromParentViewController];
self.reseau = nil;
}
This generally solves this issue (where the child view controller was deallocated). It won't solve your reseauVC problem (because you already have strong reference), but (a) you should do this wherever you do addSubview with a view controller, regardless; and (b) I show you the pattern in case you're doing addSubview elsewhere without maintaining a strong reference.
Several things. You should not add one view controller's content view to another view controller unless you set up a parent/child view controller relationship. This was added in iOS 5, and expanded in iOS 6 (and probably further expanded in iOS 7...) Look at methods like addChildViewController:, removeFromParentViewController, isMovingToParentViewController, and didMoveToParentViewController:
The easiest way to set up a parent/child view controller relationship is to use storyboards (which requires iOS 5) and an embed segue. (Which I believe was added in iOS 6). That takes care of all the housekeeping for setting up parent/child view controller relationships for you.
Your second view controller is being saved to a strong property, so I'm not clear on why it's being deallocated. I'm also not clear where the call to performSelector:withObject:withObject: is coming from. What source line is crashing, and are you using performSelector:withObject:withObject: in your code anywhere?

How is a parent view controller notified that is's child view controller has removed itself?

A child view controller is removed by the following code within the child view controller's implementation:
- (void)commandFinishVC
{
[ self.view removeFromSuperview];
[ self removeFromParentViewController ];
}
The child view controller and its view are successfully removed but how is the parent notified of this? -viewDidDisappear is not called and -viewDidUnload has been deprecated.
From the examples I can find, it is always assumed that the parent view controller is the originator of the event nominating the child being removed but (to me) the child should be autonomous in it's logic for completion.
Should the remove methods be called by utilising the self.parentViewController property and using something like the following in the parent view controller?
- (void)commandFinishVC
{
[ childVC.view removeFromSuperview];
[ childVC removeFromParentViewController ];
}
Yes, your parent controller should generally control the addition and removal of the child controllers, so your second approach seems more applicable. See the Implementing a Container View Controller section of the UIViewController Class Reference.
I suspect you could get away with your former approach, where the child is removing itself, but Apple is consistent in their documentation and the WWDC 2011 session on Implementing UIViewController Containment says that the container controller bears the responsibility for managing children.
So, in answer to your question of how a child informs a parent that it's been removed, there is no established protocol for that. But this is unsurprising because it is presumed that the parent will be initiating this process. And to the extent that a child needs to initiate this process, the documentation suggests that the parent should have a public API for managing child controllers.
So while there is no protocol for a child informing the parent, there is, however, a published API by which parent controllers inform children that they have been moved/removed. Specifically, when the parent controller is removing a child, it should call willMoveToParentViewController. This documentation explicitly says that we must perform this notification:
If you are implementing your own container view controller, it must call the willMoveToParentViewController: method of the child view controller before calling the removeFromParentViewController method...
Thus, as discussed in Adding and Removing a Child section of the View Controller Programming Guide, when removing a child, we should:
[childVC willMoveToParentViewController:nil];
[childVC.view removeFromSuperview];
[childVC removeFromParentViewController];
Unsurprisingly, the documentation for didMoveToParentViewController is equally unambiguous: When you add a child controller, you must call didMoveToParentViewController when the animation/transition (if any) is done. I don't know what would happen if we didn't call these two notification methods, but Apple says that we must and it therefore seems prudent to do so.

Does a UIView retain its view controller

Does a UIView retain it's associated view controller?
For example, in the following code the view is retained by the parent view. It would be handy if this view also retained its ViewController so that I could go ahead and release the controller in the loadView method.
- (void) loadView {
...
MyViewController* ctrl = [[MyViewController alloc] init];
[self.view addSubview: ctrl.view];
[ctrl release];
}
The alternative, I suppose, is to keep track of the controller as an instance variable and release it when appropriate.
Thanks
No it doesnt. You need a member variable, as you mentioned already.
(A view doesn't even know its own viewController)
You've got things somewhat backward: a view controller retains its view, not the other way around. Typically, one view controller manages the entire hierarchy of views that are on the screen together. While iOS 5 does allow you to use more than one view controller at a time, doing so correctly requires more than just adding one controller's view as a subview of another controller's view. For an easy to digest explanation of the process, read the preview of the View Controllers chapter of Matt Neuberg's book, Programming iOS 5, 2nd Edition. If you add one view controller as the child of another view controller, the parent will retain the child and you won't need to create a separate property for it. But do read the docs before you try it in order to avoid a lot of head scratching.

Correct loadView implementation

Apple's docs do not say what the correct implementation is for loadView.
I've found that if you implement loadView like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
}
...then you get different behaviour than if you don't implement it at all. In particular, in one 20-line project, I've found that viewWillAppear is called with a zero-size frame for self.view - unless you use Apple's default version of loadView.
Looking on Google, there are lots of "tutorials" that provide obviously-wrong loadView implementations - e.g. force-setting the size to (320,480) because the tutorial author "found that it works if I do this".
I'd like to know what the correct implementation should be.
NB: in my example above, I'm adding it to the view hierarchy inside AppDelegate like this:
[self.window addSubview:(UIViewController*).view];
I believe that in the presence of a UINavigationController or UITabBarController, Apple does some extra magic that - as a side-effect - causes a one-line loadView implementation to work OK. But I want to write it correctly, so that it always works!
NB: I've tried setting the autoresizing mask on the root view, but it doesn't change what happens:
- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
Default implementation of -loadView creates the view or loads NIB. As far as I know, there is no way to know the final size of the view at time of creation in -loadView. So the default view size is set to UIScreen.mainScreen.bounds. This is because it may be difficult to work with zero frame view in -viewDidLoad and other methods.
Your one-line implementation may look like this:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds];
}
You don't need to set the autoresizing mask, because you don't know in what context the view will be displayed. The caller is responsible to set you correct frame, autoresizing mask and similar properties.
Imagine this in a UINavigationController method:
// we are pushing new VC, view is accessed for the first time
pushedVC.view.frame = CGRectMake(...);
It is setting the correct frame, but your -loadView is called just before that -setFrame:. So during -viewDidLoad you have temporary non-zero frame, just to be able to setup subviews and internal autoresizing. After this, the correct frame is set to you and in -viewWillAppear: you have final frame.
First, there is no 'default' implementation of loadView...that method is specifically there for you to override. I do agree that Apple's docs can be a little unclear though. But loadView is called by default whenever the view of the navigation controller is accessed and no view exists (for example: UIView *view = viewController.view). It can also be called manually. But in no situation will loadView have the correct dimensions...that is, in fact, impossible. loadView is called in order for the parent view controller to get the view in the first place so it can size it appropriately. Then once it gets the view it calls viewDidLoad. This is the only code path they can use because views can load from the loadView method or the nib and they must provide a place for additional setup when views are loaded from a nib. Finally, the parent controller will resize the view and call viewWillAppear only when the view will actually appear. For example, if you push a controller on a navController that's off screen, it won't call viewWillAppear until the navController itself is placed on screen. This is done because there's no point in running that code until the controller is actually visible. This is also why you can only ever get the correct dimension in the viewWillAppear method.
Now, you noticed that if you add a controller to a standard controller none of this stuff happens. This is because view controllers aren't really intended to contain other view controllers per say. Now in iOS 5, they explicitly support the use of Container View Controllers...which is essentially a view controller that IS designed to contain other view controllers. They added a few 'convenience' methods in iOS 5 to help with this but it's not strictly necessary. The jist of all this is: if you want to add one view controller to another, you will have to manually setup all the appropriate calls to the child view controller (all loading methods, rotation events, memory warning etc). In other words, you have to make your own container view controller. When you do, though, keep in mind what I said before about the code path. It's important that you call child controller methods in the same order Apple does or stuff won't work right.
Here's some links to info:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html -Scroll down to: Implementing a Container View Controller
Also here for the view controller life cycle, which will help you figure out which calls need to be made in which order: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW1
I do recommend reading the entire View Controller Programming Guide....you can gleam a lot of information from there: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007457-CH1-SW1

Resources