Does UIView's addSubview really retain the view? - iphone

I ran into a situation that seems to suggest otherwise. In the following code snippet, if I remove the line: self.navigationController = nav, the root controller's view won't show up, suggesting to me that addSubview might not actually retain the view as otherwise suggested. Any idea?
- (void)applicationDidFinishLaunching:(UIApplication *)application {
self.testViewController = [[TestViewController alloc] initWithNibName:#"TestView" bundle: [NSBundle mainBundle]];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.testViewController];
self.navigationController = nav; //<-- if this line is removed, test view won't show up
[window addSubview:nav.view];
[nav release];
}

This line:
[window addSubview:nav.view];
does NOT add a view to the screen immediately. It is displayed by the OS in some future run loop on a possibly different thread. The actual implementation we can't be sure of.
This is why Apple defines delegate methods like viewDidAppear/viewWillAppear, otherwise we would not need them as we would know precisely when these events occur.
Moreover, adding a subview as you said, does indeed retains the view. It does NOT however retain the view controller or the navigation controller. Since the navigation controller WILL retain any added view controllers, we do not have to back them with an ivar.
But, your reference to the navigation controller must persist beyond the scope of the method. or depending on your code it could be dealloc-ed or have its reference lost.
So you must keep a reference to the navigation controller with an ivar and set it like so:
self.navigationController = nav;
So even though nav.view contains a pointer to testViewController.view, the application has no reference the navigation controller and, by extension, the view. The result is a blank screen.
To make this more obvious that it isn't a retain/release problem, you are actually leaking in the following method:
self.testViewController = [[TestViewController alloc] initWithNibName:#"TestView" bundle: [NSBundle mainBundle]];
You need to autorelease to balance out your retain/releases by:
self.testViewController = [[[TestViewController alloc] initWithNibName:#"TestView" bundle: [NSBundle mainBundle]] autorelease];
So, that means your view has never, ever been deallocated any time you have ran this code. Which further assures us that your issue is indeed a lost reference.

The problem probably isn't that the view isn't retained, it's that the controller isn't retained.
Without this line:
self.navigationController = nav
Nothing is retaining the navigation controller. It would be strange to have the view outlive the controller.

This doesn't look lika a retain/release question to me. You view won't show up if you comment out self.navigationController = nav; because then in the next line, [window addSubview:self.navigationController.view] your self.navigationController property won't be set. It's probably nil or it would crash but can't say for sure without more of the code.

Related

pushViewController, when to set UILabel text, and does setNeedsDisplay need to be called?

I ran into something odd today that maybe someone knows something about. I have a subclass of UIViewController and its associated NIB. I set the labels in the UIViewController methods and all that works fine.
Now from another class, I create that ViewController again because I want to reuse it. I do this:
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
vc.titleLabel.text = #"testing";
vc.myTextLabel.text = #"yo";
self.navigationController pushViewController:vc animated:NO];
[vc release];
This does NOT work. I have no idea why this does not work. I would think I would set all the labels, then show the view controller by pushing it onto the stack.
However, if I do this:
[vc.view setNeedsDisplay]; // why here???
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
vc.titleLabel.text = #"testing";
vc.myTextLabel.text = #"yo";
self.navigationController pushViewController:vc animated:NO];
[vc release];
This DOES work. This does not make sense to me. I thought setNeeds Display was called AFTER a view needs to be redrawn. If I move setNeedsDisplay to the end of the block it does NOT work. It only works at the beginning of the block which is very odd to me. Any one encounter this before or know why it works this way? Thanks.
The reason is that a view controller's view is lazily-loaded. This means the controller's view is only loaded from a nib (or via -loadView) when you access the view property for the first time. If you attempt to access the labels before the view has been loaded, they will be nil and any messages you send to them will be no-ops.
So to force the view to load, you can do this:
/* make sure the view is loaded */
[vc view];
/* Access the label properties */
vc.titleLabel.text = #"testing";
However, forcing the view to load may not be a good idea in all situations, especially if the view controller is not going to be displayed immediately and you want to save memory.
In this case you can create the labels in the controller's init method so they always exist, and add them to the view controller's view manually in -viewDidLoad, rather than in your nib. This will allow the standard lazy-loading behaviour to work, but users of your class can still set properties on the labels before the view is loaded.
An alternative is to expose simple NSString properties with associated ivars on the view controller to represent any titles or text in the view. Then in your -viewDidLoad you can set the text of the labels to the value of these properties. Users of your view controller can then set these properties before the view has loaded.

NavigationController initWithRootViewController dealloc

I have some pretty simple code where I am using a UINavigationController and adding a rootViewController. After some processing has occurred I want to pop off the current view controller and replace it with another one. This seems to work fine, but my original view controller does not dealloc. I set a breakpoint in it's dealloc and it never gets hit. Below is my code. Not sure why happens. Just for testing if I release startController twice it does go away.
StartViewController *startController = [[StartViewController alloc] initWithNibName:#"StartViewController" bundle:[NSBundle mainBundle]];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:startController];
[nav pushViewController:startController animated:NO];
self.navController = nav;
[startController release];
[nav release];
Thanks any help.
Your ultimate goal is to bring the view controller's retain count to zero. So make sure that everything the view retains is released and anywhere the view is retained also release.
Please check the following possible causes:
Make sure you pop the view controller from the navController if you have a custom back button. The default Back button will work fine.
Make sure that all your IBOutlets are set to nil in viewDidUnload
- (void)viewDidUnload
{
[super viewDidUnload];
self.webView = nil;
}
If your view is an observer to a model class to receive events
For example
model.addObserver(myView);
and sure to also do
model.removeObserver(myView);
I hope this helps.
It looks as though your self.navController has got a holding reference to it. maybe put
self.navController =nil;
somewhere appropriate, so that when the view has been popped it is released.
I was trying to pop off the root view controller. I instead used the setViewControllers message from the UINavigationController object to replace all my view controllers.

viewWillAppear is never called, till I load a modal view controller in the of the view, after that viewWillAppear is behaving as we expect! [duplicate]

I'm adding a view with -addSubView. I'm also using the view elsewhere and presentModalViewController.
-viewWillAppear is called with presentModalViewController but not with addSubView.
No doubt this is something obvious ?
viewWillAppear: is called by the parent view controller when it presents another view controller (e.g. by pushViewController:animated: or presentModalViewController:animated:). It is never called when you manually call addSubview:.
The above answer does not appear to be correct, in iOS 6.1 anyway. When I create a view with a controller like this:
self.welcomeController = [[DPLWelcomeViewController alloc] initWithNibName:#"DPLWelcomeViewController" bundle:nil];
self.welcomeController.view.frame = self.window.screen.applicationFrame;
self.welcomeController.delegate = self;
[self.window.rootViewController.view addSubview:self.welcomeController.view];
-viewWillAppear does get called (as long as self.welcomeController is a strong property).
On the other hand, when I did it like this:
DPLWelcomeViewController *welcomeController = [[DPLWelcomeViewController alloc] initWithNibName:#"DPLWelcomeViewController" bundle:nil];
welcomeController.view.frame = self.window.screen.applicationFrame;
welcomeController.delegate = self;
[self.window.rootViewController.view addSubview:welcomeController.view];
ARC cleaned it up once this method exited, and as best I can tell, -viewWillAppear got sent to nil. (Notwithstanding that I could see the Welcome view on my screen -- I believe rootViewController.views retained the view, but nothing was retaining the viewController.)

when to alloc and initialize a view controller

recently I joined two Xcode projects together. To get this thing to work, I had to alloc and initialize my view controller.
self.myViewController = [[MyViewController alloc] init];
But why? In the other project I have the same code. The sole difference is hierarchy of the different views. I added a new view to the top (beginning). So the calling View Controller is not the first view on the stack anymore.
I'm pushing my view in this way on the stack:
[[self navigationController] pushViewController:myViewController animated:YES];
In my NIBs I have added a View Controller object with IB and connected the Outlets.
And I have a memory management question too: If I have a property like myViewController, do I have to release it? The "normal" release is done in the dealloc method. But do I have to use an additional release because of the alloc? I don't think so, but I ask you anyway.
I would need to see more code to answer why you had to alloc your view controller, but I'd say that you always alloc them manually (at least in my experience).
As for the memory management question, if your property is declared as a retain property (#property(retain) UIViewController *myViewController), you are indeed leaking memory, since the retain count after alloc will be 1, and after the retain done by your accessor will be 2. Hence, if you release it only once, you'll end up with a leak.
I usually do this instead:
self.myViewController = [[[MyViewController alloc] init] autorelease];
I found it out: In IB I had to set the nib name on my view controller object. So the alloc and initialization is done by IB?
There is one more option:
(IBAction)loginButton:(UIButton *)sender {
NSLog(#"pressed login");
ICMasterViewController *controller = [[self storyboard] instantiateViewControllerWithIdentifier:#"mainnav"];
[self presentViewController:controller animated:YES completion:nil];
}
On your storyboard you must have UIViewController with name mainnav

Why doesn't initWithRootViewController retain the viewController Class that is passed into it?

I have a custom viewController called SourceListViewController, and I'm adding it into a UINavigationController, the view of which is then added to the window of the iphone App. After passing the SourceListViewController to UINavigationController, I release the sourceListViewController.
SourceListViewController *sourceListVC = [[SourceListViewController alloc] initWithNibName:#"SourceListViewController" bundle:nil];
UINavigationController *navigationController = [[[UINavigationController alloc] initWithRootViewController:sourceListVC] autorelease];
[sourceListVC release];
When I do this, the app would crash after the view is loaded onto the phone. When I commented out the last line, the app work fine. Isn't initWithRootViewController supposed to retain the copy of sourceListVC?
You are autoreleasing navigationController. So if navigationController gets autoreleased (which will probably happen in the next runloop) then so will sourceListVC.