I have an app which is based on a UINavigationController. There is a menu screen with buttons that segue (push onto navigation controller stack) to one of 9 other "sub-screens". None of these sub-screens segue to any other screen. When a user is done inputing data on a "sub-screen" they can press a done button which will pop back to the original menu screen. (If you're having difficulty picturing this, imagine a tree like storyboard where there is one root ViewController and then 9 leaf viewControllers).
Ok, so with that setup I have a few questions about how viewDidLoad works.
~ First, is viewDidLoad supposed to be called every time we transition to a sub-screen. For example, suppose I go from the menu screen to sub-screen "B", back to the menu screen and then back to sub-screen "B". Should B's viewDidLoad method be called twice? If not, why might mine be getting called twice?
~ Second, assuming that it will get called each time, what do I do if I have a lot of long operations that need to be performed exactly one time for each sub-screen? Where should I put them (if I put them in viewDidLoad it would happen multiple times if the user kept going back and forth between this page and the menu)?
To answer your questions:
Yes, in general B's viewDidLoad method should be called each time that it is pushed onto the UINavigationController's stack. This is because each time that B is popped off of the stack it is typically released, and each time that you go to B a new instance of B is created.
There could be numerous ways to handle this type of situation. It is hard to tell what is right for you without seeing exactly what you are trying to do. One way would be to create a singleton object that handles the processing. The reason this might be better than handling it within your UIViewController is that a singleton can live throughout the lifetime of the application, whereas UIViewControllers typically have a relatively short lifespan. Singleton objects can be created just once and they can manage whatever operations and data that you need to persist through the lifetime of your application.
1.
viewDidLoad is called when the view is loaded, and viewWillAppear is called when the view becomes visible.
If your viewDidLoad is called several times that means that you are loading the view every time you are showing it and releasing it every time you are popping it. If you post some code I could help you identify the problem better.
What you could do is something like this:
In your "root" viewController class, declare each "leaf" ViewController as a member, lets say they are called leafController1, leafController2 etc and create retain-properties for them.
#interface YourRootViewController : UIViewController {
LeafController1Class *leafController1;
LeafController2Class *leafController2;
// ...
}
#property (nonatomic, retain) LeafController1Class *leafController1;
#property (nonatomic, retain) LeafController2Class *leafController2;
// ...
#end
In the ViewDidLoad of your top ViewController, init all the leaf-controllers using "initWithNibName" etc (or whatever you are doing to create them). Retain their instances like so:
self.leafController1 = [[[LeafController1Class alloc] initWithNibName:#"LeafController1NibName" bundle:nil] autorelease];
When the user presses a button, push the correct leaf to the navigationcontroller:
[myNavigationController pushViewController:leafController1 animated:YES];
When you pop the leaf controllers now, they will be kept in memory since you retained them.
This way your viewDidLoad will only be called once for each leaf, just as long as you always push the same instace of the viewcontroller to your navigationcontroller.
2.
Heavy code related to the view should be executed when the view has been loaded, i.e. triggered by viewDidLoad. But also it might be a good idea to keep other classes that hold info about your application which are not viewcontrollers and separate from the UI. Heavy computations is better made in the background, or when the app is loading for the first time.
viewWillAppear gets called every time the view appears. viewDidLoad ONLY gets called when the view is constructed - so for example after a view controller initFromNibNamed call when the view is accessed. viewWillAppear is called anytime your view controller was not in view but comes into view - so when your view controller is pushed, viewWillAppear is called. So you might think your viewDidLoad is being called twice, but in reality it's probably not. So you should put the methods in viewDidLoad. What are you doing that takes a long time?
Related
normally after a modal transition, the second viewController runs further "behind" the visible ViewController in the background.
Is there a possibility to completely unload the second ViewController ??
If don't want to use push, because I want an animation on the transition...
Several points:
You are worrying about something that is not a worry. View controllers are lightweight, simple objects. The only heavyweight object is the view, and it is in fact removed from the interface when another view controller's view is presented in its place.
The game does not "run in the background". If it does, you're doing it wrong. You should be detecting in viewDidDisappear: that your view is no longer in the interface and stopping all the activity. That's what these sorts of events are for.
If you're still bent on making certain the view controller itself is destroyed when the game is not showing, then use a different architecture. For example, present the game controller. When the game controller is dismissed, it will be released and destroyed.
I am using storyboard and i stumbled upon something i have not been able to grasp.
I am putting up my view controller programmatically with performSegueWithIdentifier:.
It works like a charm, but what i noticed was that every single time i do this, i create a new instance of that viewController, and so, i have memory that keeps piling up. When i simulate a memory warning, i see that for each time i have been calling performSegueWithIdentifier:
i have a new instance of the view controller, and it NEVER gets deallocated. So memory just piles up and i cannot release it, which eventually causes a crash.
I just go to the view controller like this:
// If sales are registered, go to view
[self performSegueWithIdentifier:#"previousSaleSegue" sender:self];
What am i missing here?
You'll be stacking view controllers on top of each other instead of returning to a previous one. Assuming you are using a navigation controller, you'll be doing this:
A --push--> B --finished! Push---> New A ---Push---> B ---Finished! push--->A ...
What you should be doing is:
A --push--> B --finished! Pop -
^-----------------------------/
You're using modal segues by your comments, in this case you need to add an action to your return button to dismiss the view controller (returning to the previous one) instead of presenting another instance. The principle is the same. You'd use
[self dismissViewControllerAnimated:YES completion:nil];
If you're certain that viewcontroller instance does not get deallocated it must be because you keep reference to it somewhere (it could be a cycle on the controller itself).
I'm using Storyboard first time, and I've created UITabBarController application. I've got 4 UINavigationControllers in UITabBarController. At the start only first UINavigationController with root UIViewController initializes, but other 3 initizlize only when I select them. How to init them all? I need instance each of them at the start.
For example I need to set data from first UIViewController to the fourth, but to do that now, I need to select it first. I've put breakpoints in each UIViewContoller in the viewDidLoad method, and breakpoint enables only when I select this UIViewController.
Is there way to init them all?
Im am pretty sure that the UINavigationControllers together with their root UIViewController are instantiated as soon as you load the UITabBarController from the storyboard. Therefore you should be able to send messages to them.
But the view of a UIViewController is loaded only when needed, therefore the viewDidLoad method is only hit when you select that tab.
The view of a view controller can also be unloaded at runtime, when the view is not visible and memory is low.
I have been doing spring cleaning in my app. I noticed something strange, that, when I tried to correct it, completely crashes my app.
There are two "paths" in my app; either you are in the "A" part of it or you are in the "B" part. From the "A" part you can go to the "B" part and the other way around.
I designed it so that the app delegate class has two methods; switchToAView and switchToBView
So being in the middle of the BView and calling the switchToAView method on the app delegate should completely release everything related to the BView and send the user to the AView.
I found out when switching to the AView the BView view was still retained.
Each view is held by a property.
interface;
#property(nonatomic, retain) UIView *viewA
#property(nonatomic, retain) UIView *viewB
implementation;
#synthesize viewA, viewB
in the appDelegate.
I put a breakpoint in the dealloc method of the two views. (viewA and viewB).
Now this happens;
When loading up viewA and from there switching to viewB, nothing is released(as expected). Then when I switch from viewB to viewA, viewA is first released (by the generated setter, as far as I can figure out).
and then viewA is initialized and displayed.
This works just fine but has the downside that both viewA and viewB is alive on the stack at the same time :/ this is not desirable as they are mutually exclusive. The viewA is only released when a new viewA comes along, however, viewA should be released when a viewB is added to the window
I then tried releasing the viewA in the switchToBView method.
This works fine when going from viewA to viewB. I checked with instruments and the retain count drops to 0 for the viewA. Now when switching back to the viewA, the app crashes, and I think it is because the:
self.viewA = newlyInstantiatedViewA;
setter, first sends a release message to the viewA, but I released viewA earlier, and this causes the crash.
I can't get my head around that using a setter this way, will cause a crash. ("message sent to deallocated instance" and breaking in the dealloc method of the last subview added to A when [super dealloc] is called)
Should I have chosen a different approach all together?
And how will I build something sturdy, that ensures only one view is kept alive at a time?
Sorry for the lengthy writing, I would have written less if I had more time:
Thanks you in advance for any solution or design suggestions:)
Should I have chosen a different
approach all together?
Quite definitely, you mixed view controller logic into your views and you have views mutually retaining each other in what appears to be a circular reference.
(1) The views should not have logic for loading other view. Loading and unloading views is the function of the view controller. A view should only be concerned with logic immediately relating to the display. It should not even store or manipulate any user data.
(2) View controllers should seldom call other view controllers. The view controller should be concerned with managing the view and reading and writing data from the view to the data modal.
(3) If you need to relate views, use a UINavigation controller. It's not just for strictly visually hierarchal view patterns. Plus you can hide the nav bar so the user never sees it. You need to have a view controller that pushes viewA and when you need viewB, call the nav to pop viewA and push viewB. To the user, it will look like the views are simply swapping our. See the utility flip view for a similar use.
Your app works temporarily because there is nothing in the language or API that prevents you from cramming almost the entire application logic into a view subclass. However, keeping such a design running and reliable is nearly impossible.
Does the addSubview method actually load the view into the application or not? The reason I am asking is because I have two two views in my application. The application delegate adds the two views as subviews and then brings one of the views up front. Now, I have a print statement in each of the viewDidLoad methods for each view. When I run the application, the application delegate loads the views as subViews and as each view is loaded, I actually see the console print out the statements that I placed in each of the viewDidLoad methods. Is this supposed to be doing this?
viewDidLoad is actually a method of UIViewController, not UIView. It gets called after the view gets loaded into memory (after your init method, but before the awakeFromNib). You'll notice that addSubview: takes a UIView as a parameter, so the view must have been loaded in order for the view to be added to another view. Otherwise you'd be trying to add an imaginary view.
In answer to your question, yes it is supposed to be doing this. viewDidLoad is called long before you addSubview. In fact, if you take out the addSubview: lines, you'll notice that it's still getting called (because you're creating the view's controller).
My understanding is that views are lazily loaded. If your viewcontroller has 10 view, they are not all loaded until you actually try to access them.