Correct loadView implementation - ios

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

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.

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.

Displaying UIViewController inside a UIPopoverController

I am trying to present a UIViewController inside a UIPopoverController. I've designed a view controller in Interface Builder, gave it a custom class (let's say MyAppTestViewController) and I'm trying to create a popover this way:
MyAppTestViewController *fxViewController = [MyAppTestViewController new];
self.fxPopover = [[UIPopoverController alloc] initWithContentViewController:fxViewController];
self.fxPopover.popoverContentSize = CGSizeMake(1024, 120);
[self.fxPopover presentPopoverFromBarButtonItem:_fxButton permittedArrowDirections:UIPopoverArrowDirectionDown animated:NO];
When I press the button, a popover is displayed at the correct place with the correct size, but it is empty. I've verified that the MyAppTestViewController's viewDidAppear method is being called (by NSLog), but the Popover's inside is empty:
Am I missing something?
I see that you mentioned in a comment that you're using a storyboard. So why not use a popover segue to your MyAppTestViewController? You could wire the segue directly to the Effects button on your toolbar. (Or, alternatively, call performSegueWithIdentifier: from your presenting view controller.) You might do a quick test by just throwing a UILabel into MyAppTestViewController right on the storyboard and seeing if it displays properly.
I think the problem is here:
MyAppTestViewController *fxViewController = [MyAppTestViewController new];
Generally you would use [[MyViewControllerClass alloc] initWithNibName:nil bundle:nil] (assuming you have a xib file with a matching name). I don't believe I have ever seen a view controller initialized with new. Everything in Objective-C is alloc-init.
Apple Docs: UIViewController Class Reference
Quote:
To initialize your view controller object using a nib, you use the
initWithNibName:bundle: method to specify the nib file used by the
view controller. Then, when the view controller needs to load its
views, it automatically creates and configures the views using the
information stored in the nib file.
EDIT:
Fascinating, well okay. It looks like you are right about the use of the new keyword, here is bit of an explanation of this.
So fine, that's not the problem. Have you tried breaking on the viewDidAppear method and using the debugger to print out the view properties, check its frame, check its superview, and so on, try to understand the problem better? You may already know how to do this, but the commands would be po self.view and so on.
In any case, I also found this, although it only goes into the mechanics of popover presentation and not content view assignment, which you seem to have down.

how to set view controller programmatically for subview in storyboard?

(Designed in storyboard , screenshot below) I have two subviews on my rootviewcontroller's view
In my code i want to assign a separate view controller to each subview. i.e Assign a tableViewController to the TableView.
I tried to do this in awakeFromNib (or ViewDidLoad) method but to no avail. The delegate method in my tableview controller are never called. I think storyboard does the job of loading the subviews here even before the tableviewcontroller i assign can do something.
self.myTableViewController = (TodoListViewController *)[[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.myTableView.delegate = self.myTableViewController;
self.myTableView.dataSource = self.myTableViewController;
self.myTableViewController.tableView = self.myTableView;
I am not sure if this is allowed when having views like this in storyboard or i am doing anything wrong ?
I came to this site as I had a similar problem. Actually I am doing the exact same thing: I have a viewcontroller with two subviews (all defined in a storyboard with lots of constraints).
in the containerviewcontroller in viewDidLoad I am doing the same calls as you do (but I defined the view first):
self.myTableViewController = (TodoListViewController *)[[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.myTableViewController.tableView = self.myTableView;
self.myTableView.delegate = self.myTableViewController;
self.myTableView.dataSource = self.myTableViewController;
That works for me (I guess, here is your problem: You don't have a navigation controller around it.... and be sure that the outlets of your tableview are really connected in the storyboard.).
But the real problem comes after the correct wiring. If you tab into a cell, you probably want to give some cellID to the nextView. As you have defined your tableviewcontroller manually, only your delegate method didSelectRowAtIndexPath gets called, but not the prepareForSegue.
I have played around with instantiating the viewcontroller from the storyboard
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"aStoryboard" bundle:nil];
self.myTableViewController = [storyboard instantiateViewControllerWithIdentifier:#"myTableViewID"];
but the segue did not get called. calling it directly (performSegueWithIdentifier:...) did not work as this tableviewcontroller is not managed by a navigation controller.
For me it ended up that the prepareForSegue method gets called in the containerViewController which can delegate the calls to its subviewcontrollers.
EDIT: Actually the real solution is DON'T. There is a "Container View" object which can be added to a view and defines a region of a view controller that can include a child view controller. This is what I really wanted to do.
Try again with your viewDidLoad method, that is the simplest answer. If the method is not loading you may have to look into the other things inside you method because if the application is large as they often are using storyboards you may have conflicting methods.
I would also look at this:
http://blog.waynehartman.com/archive/2012/01/07/uistoryboard-on-ios-5-the-good-the-bad-and-the.aspx
It shows the most common accidents people make when using any storyboard function programatically
Hope that helps!
Sounds like you want to write yourself a custom container controller, e.g. similar to UISplitViewController. Here's apple's brief docs on doing this in the UIViewController class reference. You could for example instantiate the children controllers programmatically in your container controller's viewDidLoad: or viewWillAppear: methods. I don't think you can get IB to instantiate the children for you, though, in the same way you can wire up say a tab bar or navigation controller's relationships to their children. (If there is a way, I'd like to know!)
It's typically easiest to set your classes and delegates all in the storyboard (as shown in numerous totorials including this one).
If you're really trying to put a scroll view and table view into the same view, then you'll need to look into UIViewController instantiateViewControllerWithIdentifier:, you can reasonably easily pull multiple view controllers (ideally with their proper classes, delegates and sources set in the storyboard) in and add their views to your outer wrapper view. I will say that I've done this and you can do cool things with it reasonably easily, but it usually isn't the cleanest way to do things.

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