what's the difference of pushViewController and addSubview - ios

I create a ViewController named "YLJTestViewController" by interface builder ,code is like:
-(IBAction)DoneButtonPressed:(id)sender
{
YLJTestViewController *testViewController = [[YLJTestViewController alloc]initWithNibName:#"YLJTestViewController" bundle:nil];
[self.navigationController pushViewController:testViewController animated:YES];
//[self.view addSubview:testViewController.view];
}
but when I use [self.view addSubview:textViewController.view];it crashed,but use [self.navigationController pushViewController:testViewController animated:YES];it works well,so what's the difference?I thought they are the same...

pushViewController is like adding a piece of paper onto a stack of paper, while addSubView is like gluing a piece of paper onto another paper.
There is no explicit relationships between the previous view and the new view of the view controller which is pushed (like the pieces of paper are still separated in the stack). While the parent view will keep a strong reference to its subviews (like glue).

-addSubview: is a method of UIView. It inserts a view into another view. Like adding a button on a page.
-pushViewController: is a method of UINavigationController. It pushes a view controller onto a navigation stack. Like sliding from a table view to a details view.
In short, -addSubview: composes a view. -pushViewController: is a transition between views.

As sptrakesh states in this Apple Support forum thread:
addSubview is a lower level feature, which you use to add additional
views to your parent/main view. pushViewController replaces the
current main view in your window with the view associated with the new
view controller. You use presentModalViewController when you want to
display a view modally (blocks previous view) on top of your current
view. If you use full screen for your modal view controller, there is
not too much difference between pushViewController and this in terms
how the UI behaves. When you use pushViewController you can "pop" to
any view controller in the array of view controllers that have been
pushed, which is not as easy to do with nested modal views.

In your case the problem is not the use of addSubview: vs. pushViewController:animated:, but simply a typo when you use addSubview:.
[self.view addSubview:textViewController.view]; // misspelled
Should be (replacing x with s)
[self.view addSubview:testViewController.view]; // correct
As for the difference between addSubview: vs. pushViewController:animated:, others have already made good answers. Basically you should use pushViewController:animated: when you replace your entires screen's content, and addSubview: when you add non-full screen UI elements to an existing view.
When we are talking about the view of a UIViewController, pushViewController:animated: should be your preferred method.

I've recently ran into similar problems with addSubview and pushViewController. Everyone here has made great comments, but I would add one things:
Usually addSubview is not used by itself. You usually are using it with presentModalViewController, or in the case of controller containment, addChildViewController.
So in summary:
If you are using navigation controllers, you use pushViewController/popViewController to navigate through your app.
If you are manually switching views, use presentModalViewController.
If you are doing controller containment, use addChildViewController.
If you are using story boards, use Segues.

Related

Creating complex VC Hierarchy using NIB

I have MainVC than contains 2 tabs: FirstVC and SecondVC.
Then I tap on some of this taps I want to present below desired View/VC.
I am working on this project with Nib, so I have some confusion about it.
That object should I use here? View? How?
How I have MainVC with 2 View that hidden/shown based on tab.
In FirstVC I need to load tableView. In SecondVC - simpleView
So, can somebody give me some advices how to achieve this thing more cleverly?
You might want to use Container View Controllers, I blogged about that awhile back. (See http://www.notthepainter.com/container-view-controllers/)
Text pasted here for the future:
OS5 added something a lot of iOS developers have been needing, container view controllers. This lets you put a view controller inside another view controller. This is wonderful for countless reasons, but the one that draws me is reuse and abstraction.
I was working on a app which had 2 similar windows, they had a top part (body) and a footer. The top part was easy, they each had their own UIViewControllers. But when I went to add the footer to the second one I got to thinking DRY, I was repeating myself and that’s never a good idea. So I abstracted out a parent class and I went to put the footer code in and I got stuck, I wanted my footers to also be view controllers.
I remembered a talk I attended about new iOS5 features and container view controllers was mentioned. After a bit of googling around I had it.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// set up the footer contained view, this has nothing to do with table footers, this is below
// the tableview
//
FooterViewController *vc = [[FooterViewController alloc] initWithNibName:#"FooterViewController" bundle:nil];
vc.view.frame = self.footerFPOView.frame;
vc.delegate = self;
[self addChildViewController:vc];
[vc didMoveToParentViewController:self];
[self.view addSubview: vc.view];
}
Of course there are a few things to note. First, hooray, I’m loading my footer from a xib file. I’ve placed a UIVIew called footerFPOView in the outer view controller’s xib, this is a trick I use all the time. FPO stands for For Position Only and that lets me use interface builder for positioning. I communicate with a protocol, hence the delegate. And then I call addChildViewController to add it, and then I tell the new one that it has a new parent, and finally, add its view.
This is just a few lines of code yet the window should respond to both view controllers and respond to rotations.

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.

What does addChildViewController actually do?

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.

How do I present a View Controller and dismiss all others?

I have about 20 View Controllers, chained together with Modal and Push segues. Now, at the last View Controller I want to switch back again to the first View Controller, as if the user has restarted the app. Unfortunately when I do this with
[UIViewController *viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"InitViewController"]];
[self presentViewController:viewController animated:YES completion:nil];
all of the previous view controllers are not unloaded. Not a single viewDidUnload method is called. How can this be done?
The instantiateViewController method creates a new copy of your view controller. Your existing view controllers aren't unloaded because iOS doesn't know that you want to 'go back', so to speak. It can't unload any of your existing view controllers because they're still in the navigation hierarchy. What you really want to do is 'rewind' your storyboard in some way.
Fortunately from iOS 6 there's a much improved way to do this, through unwinding. This lets you 'backtrack' in your storyboard right back to the start, which it sounds like you want to do. The WWDC videos have some examples and walk throughs, and you might also want to look at this existing SO question:
What are Unwind segues for and how do you use them?
I found that it can be done easily by calling dismissViewControllerAnimated:completion: on the first view controller in the hierarchy. Fortunately that's all it is needed to accomplish what I wanted :-)

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.

Resources