For a controller that is pushed by a nav controller, if in its desired init method you create the details of the pushed controller's view, my understanding is that if the view is removed later because it is offscreen (due to low memory, for example), you may never see it again since the init is only called once when loading the view controller, and subsequent pushes may show nothing.
It would seem a better place for this view setup is in the viewDidLoad or viewWillAppear, that way it will be recreated correctly if the controller needs to build the view the next time it is pushed.
Yet I see tutorials that often put the pushed view controller's view setup in its init method; how important is this?
View setup should be done in viewDidLoad (or in loadView if you're not using a nib), for exactly the reason you're describing. If you need to know the controller's top-level view dimensions to set up the subviews, then do it in viewWillAppear:.
Usually, a view controller that is popped is immediately deallocated; if the app needs to show the same view later, it creates a new view controller for it. So in that scenario, the app won't show “nothing”.
If the app pushes or presents another view controller on top of the first view controller, the first view controller's view can be unloaded. If the app never pushes or presents a second view controller over the first view controller, the first view controller's view can't be unloaded until the controller is popped, at which time (in most apps) the controller is deallocated anyway. So in that case, setting up the view in init won't cause trouble.
But it's still bad design to set up views in init. You might change your app later to push or present a second view controller, thus creating unexpected unsafe behavior.
Also, it's common to create a view controller, set properties on it, and then push it. If the properties affect the controller's view hierarchy, then init is too early to set up the views.
Related
I know there are a lot of similar questions, but I can't find one that specifically addresses this.
Why is self.navigationController null when called in viewDidLoad, but correct when called from outside viewDidLoad?
This is my output from NSLog(#"%#",self.navigationController); The first is called in viewDidLoad, the second I add a button to the interface with a method that calls NSLog(#"%#",self.navigationController);:
NavApp[31524:11003] (null)
NavApp[31524:11003] <UINavigationController: 0x6e21190>
I know there must be some simple explanation for this behavior, I'm just curious as to what it is. Thanks!
A view controller's view is loaded when you first access the -view method/property on that controller. After the view has been loaded, the viewDidLoad method is called. This is pretty straight forward. You also have to remember that the view can be loaded/unloaded multiple times if memory warnings are received while the view is off-screen.
So viewDidLoad does not mean your view controller has been inserted into a navigation controller. In the process of pushing a view controller onto a navigation controller, its view will be accessed and loaded, but this will happen before the whole push is complete. So viewDidLoad is clearly getting called before the navigationController property has been updated.
You also have to consider that some other part of your code could be accessing the view controller's view before you even push the view controller onto the navigation controller.
So viewDidLoad is the wrong place to do what you are doing. You probably want to use a method like viewDidAppear: so you know that the view controller's view is part of the view hierarchy when it is invoked.
iOS 5 introduces the concept of custom container view controller and provides API like addChildViewController. Question: can you add a view controller as a child and still present it using presentViewController? Does doing the latter automatically make it a child view controller of the presentingViewController?
That's not how it's supposed to be used.
The parent/child relationship is for when a view controller has subviews that are managed by their own view controllers, for example a UITabBarController, where the parent view controller draws the tabs and the child view controllers draw the content of each tab.
If you present a view controller using presentViewController, it generally takes over the whole screen, or appears in a modal so that the presenting view controller is no longer in control. In that scenario there's no reason for the presenter to be the parent because it doesn't need to cooperate with the presented controller - it just gets out of the way until the presented controller is dismissed again.
Why is it that you wanted to do this? If it's just so that the view controllers have a reference to one another and can pass data, there are other ways to do this (e.g. the delegate pattern, NSNotifications, or even just a property linking the two).
I have a main view controller with two other view controllers as member variables, representing different "tabs" within the main view. They are hidden and shown when the "tabs" are tapped. All of these are in a nib file.
When I get a memory warning, viewdidunload is called and the view is dumped, as expected. However, they are never recreated and viewdidunload is never called again. The main view controller is attached to a nav controller.
What gives?
When a memory warning occurs, the system may unloads views of view controller that are not visible on screen at that time. The action which triggers the reloading of those view controller's views is something actually trying to access those views. In other words, when some code is executed which accesses viewController.view, if that view had been unloaded, it is reloaded.
In practical terms, this means that your unloaded views will usually be automatically reloaded at the point where they would be shown again. So after a memory warning, change the current view using your tab controller variant, and you should see that it is reloaded (if it was indeed unloaded).
I solved this by dumping and reloading the associated view controllers on a memory warning, but the original cause remains a mystery.
Viewdidappear is never called on the views either, I suspect this is related to whatever the explanation is.
If you're doing view controller containment you have to manage the appearance and disappearance of your view controllers at all time.
When a view controller unloads its the view whole hierarchy of that view is destroyed and is, of course, not saved. When the view of that parent is reloaded, the -loadView and -viewDidLoad code are executed as well as the appearance methods. These methods will not do more than what you tell them to do. If you do not rebuild the view hierarchy of your children in viewDidLoad don't expect them to reappear magically.
The job of the container controller is to load the view of its children (the ones that are visible) and layout them in its own view.
Some times when I push ViewController into Navigation Controller,
the viewDidLoad() method of the View Controller is not called.
And this cause my application to crash. I would appreciate any help.
I forget to mention that I load the view from the nib before I push it to the Navigation Controller.
Thanks,
Sarah
The viewDidLoad method is only called when the view is first loaded from the Nib file. If the view was already loaded and you push the view again it will not fire again.
Depending on what you want to do, you may want to use viewWillAppear or viewDidAppear instead.
Once the View is loaded and added to the Controller's stack, you will not see this called again. You would need the view to get popped off the stack and pushed again to see it. You can always be assured viewWillAppear will get invoked everytime you return to the view. This allows you to do any housekeeping that may be in order (which i assume is the goal).
I'm starting an app where I'd like to have multiple view controllers. Some of the views will be displayed inside of a navigation controller. I can create a navigation controller and then add another instantiated view controller to it. But what I'd like to do, is just instantiate a view controller that has its own view and is the root view controller of a navigation view controller. So when I instantiate the view controller, I'd like for it create a navigation controller and push "self" on to it. When I do it my simulator crashes and the details don't really give a reason. The console does not display anything. Any ideas. My reason for this is to separate out logic without have a view controller that simply creates a navigation controller and then pushes another view controller on it as the root view controller.
I'm not entirely sure if I understand your question correctly. Why would it be preferable if the view controller pushed itself to the navigation controller? I mean, you have to instantiate your view controller at some point in code (either app delegate or another view controller) anyway. Why can't you just create the navigation controller there, instantiate your VC and then push it onto the nav controller? As far as I can see, this doesn't involve creating any additional view controllers.
Anyway, having a view controller decide by itself where it is used (ie. pushed onto), is not best practice. This way you lose the flexibility of using it in other contexts. Always try to couple your components as loosely as possible.