what function should I implement to cleanup the view of a UINavigationController - iphone

I have A UINavigationController with a table view. As it's standard behaviour when an item in the list is selected I push a customViewController to the UINavigationController. The CustomView Appears and I see my back button in the title bar.
My question is, when I hit the back button in the title bar to navigate back to my list what function do I implement to make sure that everything that was created in the customViewController is completely destroyed and removed from memory?
I tried putting my cleanup code in the viewdidunload method of the custom controller but that doesnt even get entered when I hit the back button.
(Also I wasnt really sure how to phrase this question so suggestions are welcome)

Apple explains everything very clearly in their documenation (with pretty pictures and everything!). Basically, when you show the view you use pushViewController:animated: and when you go back you use popViewControllerAnimated:.
Use something like this to go to the new screen:
- (IBAction)goSomewhereButtonPressed:(id)sender {
SomewhereViewController *vc = [[SomewhereViewController alloc] initWithNibName:#"SomewhereView" bundle:nil];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
}
When the BACK button is pressed, it will clean-up your screen. Apple recommends you use UINavigationControllerDelegate for additional setup & cleanup if needed.
Put the cleanup for the screen in its controller (SomewhereViewController).
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
NSLog(#"Somewhere's viewDidUnload called");
}
- (void)dealloc
{
[super dealloc];
NSLog(#"Somewhere's dealloc called");
}

I always put my cleanup code in dealloc:
-(void)dealloc {
// cleanup code here
[super dealloc];
}

The allocated controllers inside a UINAvigationController will be removed automatically. If you need to let just one live, create the detail controller globally in your navigation controller instead every time you need to go to the detail view, so you will use always the same controller. You can clean it when the back button is pressed through the method viewDidDisappear.

The viewDidUnload method of UIViewController seems to be a good place to do memory cleanup, i.e. release all objects that can be easily recreated in viewDidLoad or later.
But it's not guaranteed that the view controller itself will be dealloc'ed. The UINavigationController may cache the object internally.

Related

How often is viewDidLoad called?

When the user switches to another program and then back again., the original program's view will be replaced by a new view from another program. So when the user switches back to the original program, would viewDidLoad be called the second time ?
Am asking this because if this is the case, then the initialization code placed inside viewDidLoad would be executed every time the user switches the screen back and forth. And this could result in reseting views and loosing unfinished works of the user ...
Don't do view controller initialisation in viewDidLoad. This is a common mistake.
For stuff that should only happen once when the view controller is loaded, do it in the controller's init method, like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:bundleOrNil]))
{
//do your initialisation here
}
return self;
}
The initWithNibName:bundle: method is called before the view is loaded from the nib, and is only called once in the lifespan of the view controller.
The controller's view can be loaded and unloaded multiple times during the lifespan of the controller and viewDidLoad will be called every time. It may be unloaded whenever it's not on screen, usually if memory is low.
If you do set stuff up in viewDidLoad (e.g. adding subviews programmatically) you should always unset them again in viewDidUnload.
Think of viewDidLoad and viewDidUnload as being like the init/dealloc for the view property of the view controller. For stuff that relates to the views, create and release it in those methods. For stuff that relates to the controller itself, create and release it in initWithNibName and dealloc.
UPDATE: On iOS 6 and later, viewDidUnload is never called any more (unless the view is explicitly set to nil in the code), and so viewDidLoad will typically only be called once in the life of a view controller. This makes the advice above less critical, but it's still best practice, and still necessary if you need to support iOS 5 and earlier.
UPDATE 2: If you are loading your view controller from a Storyboard (which is now the recommended practice) instead of creating it programmatically then initWithNibName:bundle: will not be called. Use initWithCoder: or awakeFromNib to initialize your controller instead.
#Nick Lockwood provides excellent information, but there are a few more things to remember.
First, initWithNibName:bundle: is not called if the view controller is instantiated from a nib file or storyboard. In that case, initWithCoder: and awakeFromNib are called instead. This situation used to be somewhat uncommon on iOS, but with the addition of storyboards it is now much more common for view controllers to bypass initWithNibName:bundle:.
I recommend putting non-UI initialization code in a separate method (I call mine setup) and call it from both initWithNibName:bundle: and awakeFromNib. But I only do this if it's important that that initialization only run once. Otherwise I put it in viewWillAppear: to be as lazy-load as possible.
Second, you should not do anything that references self.view in init... or awakeFromNib. You should never reference self.view until viewDidLoad is called (otherwise you will force the nib file to be loaded sooner than it is needed). UI-related things should go in viewDidLoad if they're related to setting up the views, or viewWillAppear: if they're related to configuring the views (i.e. loading them with data).
So the way I usually set these things up:
#implementation
- (void)setup {
// Non-UI initialization goes here. It will only ever be called once.
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
- (void)viewDidLoad {
// Any UI-related configuration goes here. It may be called multiple times,
// but each time it is called, `self.view` will be freshly loaded from the nib
// file.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Set all IBOutlets to `nil` here.
// Drop any lazy-load data that you didn't drop in viewWillDisappear:
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Most data loading should go here to make sure the view matches the model
// every time it's put on the screen. This is also a good place to observe
// notifications and KVO, and to setup timers.
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Unregister from notifications and KVO here (balancing viewWillAppear:).
// Stop timers.
// This is a good place to tidy things up, free memory, save things to
// the model, etc.
}
- (void)dealloc {
// standard release stuff if non-ARC
[[NSNotificationCenter defaultCenter] removeObvserver:self]; // If you observed anything
// Stop timers.
// Don't unregister KVO here. Observe and remove KVO in viewWill(Dis)appear.
}
#end
-viewDidLoad will be called once whenever the view controller needs to load its view hierarchy. Obviously, that'll happen the first time that the controller accesses its view. If the view controller later unloads its view, then -viewDidLoad will be called again the next time the view is loaded. A view controller won't unload its view just because the view is hidden, but it might do so if memory starts to run low.
A view controller should know the state of its views and be able to set them up as necessary in its -viewDidLoad method. Views shouldn't be used to store state -- nothing should be irrevocably lost just because the view is unloaded.
So when the user switches back to the original program, would
viewDidLoad be called the second time ?
(Above is from the op)
In those cases there are two methods to be called:
- (void)applicationWillEnterForeground:(UIApplication *)application;
reopening a backgrounded app (from task manager or from springboard again)
unlocking device which is locked when the app is active.
- (void)applicationDidBecomeActive:(UIApplication *)application
after phone calls
notification center dismissal
task manager dismissal (double tap home button & double tap again)
From the docs:
This method is called after the view controller has loaded its associated views into memory.
So, it is called whenever the view controller has its views loaded into memory. This could be the first time the view is loaded and never again, or every time the view is made visible if your view unloads constantly (viewDidUnload due to memory constraints, etc.)

How to dealloc uiviewcontroller/unload views on logout action

I think this is a pretty common usecase as I have seen it in several apps. But after spending couple of days, I am still struggling with this. I have a structure like the following:
UITabBarController
-- UINavigationController1
---- UITableViewController1
-- UINavigationController2
---- UITableViewController2
Now I have a logout button on UITableViewController2. When I click on that logout button I want all and any viewcontroller to be deallocated, all view unloaded. Basically start fresh like launching the app. I basically want the viewDidLoad on each of those UITableViewController called again.
I tried the following method to be called in my appdelegate when the logout action on UITableViewController2 is taken.
-(void) logout {
for (UINavigationController* ctrl in self.tabBarController.viewControllers) {
[ctrl popToRootViewControllerAnimated:NO];
ctrl.visibleViewController.view = nil;
}
[self.tabBarController.view removeFromSuperview];
[self.window addSubview:self.tabBarController.view];
}
But alas, it does not seem to work?
Any ideas how such a thing is accomplished? Also I see different behaviors in iOS4 vs iOS5 with the visibleViewController. I am not using any modal viewcontroller here. Any gotchas?
Update: I am not using ARC
thanks
mbh
Your for-loop will release and thus dealloc any view controllers that you have pushed onto the respective UINavigationController roots (depending on how many tabs you have), i.e. as these will not have a superview when you pop back to the root of each navigation controller, these are dealloc-ed automatically. These are your UITableViewControllers taken care of.
As for the respective UINavigationControllers, you would need your tabbar-controller to release the old instance. IMHO, this should be done for you when you release the UITabBarController.
This then leaves the UITabBarController itself. I don't think it can be done tbh. Your code will only remove the view, but not dealloc the tabbar controller itself. And as Krishna K points out, you need at least one view controller to reload all others.
Putting the code into the appdelegate makes sense, but you need to ensure that your logout() will not cause a retain on the UITableViewController2 as well as the UITabbarController as it's called from UITableViewController2 somewhere.
One idea to explore, does your AppDelegate hold an instance to the TabBar-Controller which you could release and create a new instance after removing the view from self.window?
// manually create UITabBarController - AppDelegate holds instance
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
}
- (void) logout {
[self.tabBarController.view removeFromSuperview];
[mytabcontroller release];
mytabcontroller=[[UITabBarController alloc] initWithNibName:#"foo" bundle:nil];
[self.window addSubview:self.tabBarController.view];
}
But as I said, there might be caveats with memory management at this point.
You need to release your view controllers. When their release method is called, that method should include statements to release all of its object's resources (and also dealloc its superclass).
Your rootViewController for both Navigation controllers are their respective TableView controllers. So I don't think popToRootViewController would do anything.
You probably need to reset the data and refresh the views instead of deallocating the views.

Release UIViewController if available

Hey,
I'm adding a UIViewcontroller as a subview of my current view. With something like that:
[self.view addSubview:viewcontroller.view];
The user can interact with that added viewcontroller so I can't just release it after I added it's view as my subview, right ?
So I would like to release it the dealloc methode and set it to nil in viewDidUnload when my master viewcontroller gets unloaded, right ?
The problem is, the viewcontoller I add as a subview is not added every time. So the question is, how can I ceck if the viewcontroller was added as subview and if so, release it.
Thx a lot !
Sebastian
You can check it like this:
if(viewController)
{
[viewController release];
viewController=nil;
}
and yes u need to put this in your dealloc method.
If you read correctly your code:
[self.view addSubview:viewcontroller.view];
it is not the controller which is being added as a subview, rather the view it manages.
This is an important point, since it means that the controller itself does not get retained. What is retained is the view.
So you can simply go on releasing your viewController in -dealloc as usual:
-(void)dealloc {
[viewController release]; viewController = nil;
...
}
and in your viewController's dealloc you will need to release the managed view, if you allocated it manually (or wherever it makes sense for your controller to release its view, if necessary).
On the other hand, whenever your superview is deallocated, then also the view you added as a subview will be released (as per Apple spec of addSubview behavior). So nothing to worry about here. Just release the viewController properly (and make that the view controller manages correctly tis own view).
One more note: you should not release your view controller in viewDidUnload.
In some open source code that I used to contribute to, we had a macro called RELEASE_TO_NIL which did exactly this.
#define RELEASE_TO_NIL(obj) if(obj != nil) { [obj release]; obj = nil; }
You would use it like this:
RELEASE_TO_NIL(viewController);
Simple as that.
Just add an tag to viewController.view and test if self.view contains that tag.
UIView *testView = [self.view viewWithTag:yourTag];
if(testView!=nil){
doStuff;
}
According to the Apple docs, a UIViewController instance should manage a view that fills the whole screen. If your view does not fill the screen, you could either subclass UIView to handle delegation or make the superview work as a delegate.
Also, the subviews of a view are contained in an instance of NSArray, do [myView subviews] to return this property. Ask the returned array if it contains the subview in question and you can release accordingly.
But, without knowing more, it does sound like you need to rethink how you're setting all this up.

Releasing UIViewController when not in use

Hi I hope somebody can help me with this problem.
I have a UIViewController named "Login" (for example) and when the user has successfully logged in this will call another controller to replace the "Login" controller like below:
[self presentModalViewController:anotherController animated:YES].
I do not need the previous controller any more so I placed a [self release] as shown in the code snippet below.
LoginController.m
- (void)viewDidDisappear:(BOOL)animated {
[self release];
}
This will then call the LoginController's dealloc method and I can be sure it's released.
Now in the new controller that is now in view has a button which calls a UINavigationController like below:
[self presentModalViewController:settingsNavigationController animated:YES];
But this crashes the app which would normally work if I didn't release the previous LoginController.
There is probably an easier or more logical method to release the controller but as I am running out of ideas I sometimes use drastic measures.
Thank you.
You should not be releasing the LoginController, at least not in its own -viewDidDisappear:. That controller is still in use and can be referenced, for example by the navigation controller's parentViewController property. Release the controller when it is no longer part of your view controller hierarchy, not just when it is no longer visible.
In addition [self release] is a warning sign that you are applying incorrect memory management.
1) Replace the "Login" controller
presentModalViewController doesn't replace your login view controller, but it puts anotherController on top of your login view controller.
2) viewDidDisappear
You should read documentation. Quote - You can override this method to perform additional tasks associated with dismissing or hiding the view. If you override this method, you must call super at some point in your implementation.
In other words, you must call [super viewDidDisappear:animated] too.
3) Memory Management
You should definitely need to read Memory Management Guide - http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/MemoryMgmt/MemoryMgmt.html
4) View Controller
You should definitely need to read View Controller Programming Guide too - http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
Two problems with what you are doing.
First, viewDidDisappear is NOT a destructor. Do not release self there. viewDidDisAppear and appear are used for visibility of the view, not in/out of memory.
Second, presentModalViewController is intended for presenting a MODAL view controller, ie child.
You should consider setting up a UINavigationController and calling
[navigationController popToRootViewControllerAnimated:FALSE];
[navigationController pushViewController:(UIViewController*)controller animated:TRUE];
also in some situations you can get away with an [autorelease] view controller using present modal.

UINavigationController and viewWillDisappear

So I have a UINavController in my app and am trying to execute a method when the user presses the back button. I have searched everywhere and can only find bits and pieces that don't really make sense out of context.
Is there a way to implement some sort of check that catches when the user presses the back button to dismiss the current view? (the viewWillDisappear method for the view being popped never gets called for some reason. I did read that it doesn't unless you forward that call?) Does that sound right, and does anyone have any ideas or suggestions? Thanks in advance.
Take a look at the UINavigationControllerDelegate. There are the only two methods that get called when a UIViewController is pushed to the navigation controller stack. Similarly, if one is being pushed then something probably was just popped. This is what I did to call viewDidDisappear and viewWillDisappear.
# pragma mark - UINavigationControllerDelegate Methods
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
static UIViewController *vcPointer = nil;
// If the previous view controller is still around then let's send a viewWillDisappear message
if (vcPointer != nil) {
if ([vcPointer respondsToSelector:#selector(viewWillDisappear:)]) {
[vcPointer viewWillDisappear:animated];
}
}
// Keep track of a pointer to the current viewController
vcPointer = viewController;
[viewController viewWillAppear:animated];
}
This code keeps a pointer reference to the last view controller that was pushed so that once we push another one we can pop the last one (if it still exists).
AFAIK, if you add a UINavigationController to a UIView via code, it won't send those messages to it's subviews by default. It will only do this if the UINavigationController received these calls itself. Maybe this is your problem (I don't know your view setup).
So, when adding the view of the UINavigationController, be sure to manually send it these messages.
UINavigationController *navigationController = [UINavigationController alloc] initWithRootViewController:rootViewController];
[navigationController viewWillAppear:NO];
[aView addSubview:navigationController.view];
[navigationController viewDidAppear:NO];
At least, this is what I found during development. Been searching for this for a long time and I still don't understand the rationale behind it.
You can always hide the default back navigation button and create your own with its own method to be called when pressed.
Execute whatever code you want there then pop the view.
I used this solution:
Add a custom button on the left side in the navigation bar
Let that button activate a custom method.
Disadvantage of this workaround: you will lose that nice arrow shaped "back" button. That can be solved as well with a custom image.
So here is my code.
Put this in your viewDidLoad:
// LeftButton in Navigation Bar
UIBarButtonItem *leftBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backButtonPushed:)];
self.navigationItem.leftBarButtonItem = leftBarButton;
[leftBarButton release];
Then add this method in the same .m file:
- (void) backButtonPushed: (id)sender {
// do what you want to do
}
dont forget in the .h file
- (void) backButtonPushed: (id)sender;
The viewWillDisappear & viewDidDisappear is called when a controller is popped or dismissed. The function is called on the fore-front view controller not on the UINavigationController itself. Did you possibly subclass and forget to call the super on something?