objective-c addSubView retain count - objective-c

I was under the impression that adding a subview to a view goes like this:
UITableViewController *sitesel = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped];
sitesel.view.frame = CGRectMake(0,0,100,100);
[self.left addSubview:sitesel.view];
[sitesel release];
But it seems I should not release the sitesel (the controller)?
So should I release the view or what, I had this retain stuff nailed a while ago, but it's slipped. (And to use a TableView, you have to subclass UITableViewController right?)
(self.left is a subview of self.view, added in a nib)

addSubview does retain the view, that's not the problem. Your issue is that the controller for the view goes away a little later.
You shouldn't release the view, because that's none of your business. You didn't create it, you didn't touch it. Leave it alone.
In order to keep things working, it needs to stay connected to a valid controller. Hence, you must not release the controller, but keep it around. Add a property like #property(retain) UITableViewController *siteController; and then do self.siteController = sitesel; before you release the controller. This way everything stays in memory.
PS: For cleanness, you should probably change the view in the accessor for sitesel. Just to make sure it always comes and goes along the controller. Your method would then get even shorter, just setting the controller.
ADDED: That setter could look like that, requiring you to set only the controller and the view being updated transparently:
- (void)setSiteselController:(UITableViewController *)ctrl {
if (_sitesel)
[_sitesel.view removeFromSuperview];
[_sitesel autorelease];
_sitesel = [ctrl retain];
if (_sitesel) {
_sitesel.view.frame = CGRectMake(0,0,100,100);
[self.left addSubview: _sitesel.view];
}
}
Your original code will then shrink to this much cleaner version:
UITableViewController *sitesel = [[UITableViewController alloc] initWithStyle: UITableViewStyleGrouped];
self.siteselController = sitesel;
[sitesel release];
PPS: You don need an controller for a UITableView to work. It's just much simpler!

Related

releasing UIViewController subclasses does not actually free up memory

I have a UINavigationController object (named LoginNav) which consists of ViewController1 & ViewController2, my iPad app starts by loading a UISplitViewController subclass (named mainSplitViewController) and then presenting LoginNav modally on top of it (this is by the way done in didFinishLaunchingWithOptions method of AppDelegate like this:
[self.mainSplitViewController presentModalViewController:LoginNav animated:YES];).
Once ViewController1 is shown, I tap a UIButton in it to push ViewController2, when I finish working in ViewController2 I tap a UIButton in it to call [self.navigationController dismissModalViewControllerAnimated:YES]; to dismiss LoginNav with both of its view controllers and show mainSplitViewController's contents.
There is a dealloc method in both ViewController1 & ViewController2 with NSLog statement in each one, once loginNav is dismissed, the NSLogs never get fired, but doing [self.navigationController.viewControllers objectAtIndex:0] release]; & [self.navigationController.viewControllers objectAtIndex:1] release]; right after [self.navigationController dismissModalViewControllerAnimated:YES]; fires both NSLogs.
I commented out the above two release statements, then I launched the Allocations instrument, and launched the app again and pushed ViewController2 then dismissed loginNav as described above, and looked at Live Bytes column (All Allocations value ) it was 6.9 MB right after the dismissal of loginNav, then I did this step again but in this case using the two release statements, I got exactly a 6.9 MB value on Live Bytes column.
Two Questions:
1) why do not the dealloc methods of ViewController1 & ViewController2 never get fired after the dismissal of the navigation controller LoginNav that holds them ? and is it correct to do the above two release statements to release these view controllers ?
2) why releasing ViewController1 & ViewController2 does not free up memory ?
p.s. there is no single variable (or IBOutlet) being held in memory in both ViewController1 & ViewController2, everything is released in both of them.
These kinds of issues are nearly impossible to troubleshoot without seeing all your code. When you manage memory manually, there are multiple areas that you can go wrong. For example the following code will leak:
- (void)didSelectSomethingInViewControllerOne
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:vc2 animated:YES];
}
In this case you have allocated the object and thus have ownership of it. Then the nav controller takes ownership of it. When you pop the controller from the navigation stack, the navigation controller relinquishes ownership of it, but you never did, so it still has a retain count of 1 and won't get deallocated.
Relinquishing ownership of the controllers later in your code (like after dismissing the modal view) is a bad idea. It makes it difficult to analyze ownership when your releases are all over the place. As soon as the navigation controller has ownership you can release the object you allocated, as you do not intend to use it in the future:
- (void)didSelectSomethingInViewControllerOne
{
ViewController2 *vc2 = [[ViewController2 alloc] init];
[self.navigationController pushViewController:vc2 animated:YES];
[vc2 release];
}
The situation above can have nothing to do with your problem. Your problem may reside in many different areas. Which is why troubleshooting memory management problems is difficult. Without seeing source code.
Consider transitioning your project to ARC:
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
Another thing you might have fallen foul of is a retained property (such as a delegate) in your LoginNav.

Replacing Storyboard Segue with pushViewController causes strange behaviour

I can't seem to figure this out for the life of me. I have a custom table view cell, in that cell I have a few buttons configured. Each button connects to other view controllers via a storyboard segue. I've recently removed these segues and put a pushViewController method in place. Transition back and forth across the various views works as expected however the destination view controller is not displaying anything! I have some code below as an example.
Buttons have this method set:
[cell.spotButton1 addTarget:self action:#selector(showSpotDetails:) forControlEvents:UIControlEventTouchUpInside];
// etc...
[cell.spotButton4 addTarget:self action:#selector(showSpotDetails:) forControlEvents:UIControlEventTouchUpInside];
// etc...
showSpotDetails Method contains this code:
- (void)showSpotDetails:(id)sender
{
// determine which button (spot) was selected, then use its tag parameter to determine the spot.
UIButton *selectedButton = (UIButton *)sender;
Spot *spot = (Spot *)[spotsArray_ objectAtIndex:selectedButton.tag];
SpotDetails *spotDetails = [[SpotDetails alloc] init];
[spotDetails setSpotDetailsObject:spot];
[self.navigationController pushViewController:spotDetails animated:YES];
}
The details VC does receive the object data.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"spotDetailsObject %#", spotDetailsObject_.name);
}
The NSLog method below does output the passed object. Also, everything in the details view controller is as it was. Nothing has changed on the details VC. It just does not render anything ever since I removed the segue and added the pushViewController method. Perhaps I am missing something on the pushViewController method? I never really do things this way, I try to always use segues...
Any suggestions?
Welcome to the real world. Previously, the storyboard was a crutch; you were hiding from yourself the true facts about how view controllers work. Now you are trying to throw away that crutch. Good! But now you must learn to walk. :) The key here is this line:
SpotDetails *spotDetails = [[SpotDetails alloc] init];
SpotDetails is a UIViewController subclass. You are not doing anything here that would cause this UIViewController to have a view. Thus you are ending up a with blank generic view! If you want a UIViewController to have a view, you need to give it a view somehow. For example, you could draw the view in a nib called SpotDetails.xib where the File's Owner is an SpotDetails instance. Or you could construct the view's contents in code in your override of viewDidLoad. The details are in the UIViewController documentation, or, even better, read my book which tells you all about how a view controller gets its view:
http://www.apeth.com/iOSBook/ch19.html
The reason this problem didn't arise before is that you drew the view in the same nib as the view controller (i.e. the storyboard file). But when you alloc-init a SpotDetails, that is not the same instance as the one in the storyboard file, so you don't get that view. Thus, one solution could be to load the storyboard and fetch that SpotDetails instance, the one in the storyboard (by calling instantiateViewControllerWithIdentifier:). I explain how to do that here:
http://www.apeth.com/iOSBook/ch19.html#SECsivc

UIViewController and UIImagePickerController: Unable to create and managing views as expected

I have a UIViewController subclass that contains an instance of UIImagePickerController. Let's call this controller CameraController. Among other things, the CameraController manages the UIImagePickerController instance's overlayView, and other views, buttons, labels etc. that are displayed when the UIImagePickerController, let's call this instance photoPicker, is displayed as the modal controller.
The photoPicker's camera overlay and the elemets that are part of the CameraController view hierarchy display and function as expected. The problem I'm having is that I cannot use UIViewController's default initializer to create the CameraController's view heirarchy.
I am initializing CameraController from within another UIViewController. Let's call this controller the WebViewController. When the user clicks on a button in a view managed by WebViewController, the launchCamera method is called. It currently looks like this:
- (void) launchCamera{
if (!cameraController) {
cameraController = [[CameraController alloc] init];
// cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
// bundle:[NSBundle mainBundle]];
cameraController.delegate = self;
}
[self presentModalViewController:cameraController.photoPicker animated:NO];
}
I want to be able to create CameraController by calling initWithNibName:bundle: but it's not working
as I'll explain.
CameraController's init method looks like this:
- (id) init {
if (self == [super init]) {
// Create and configure the image picker here...
// Load the UI elements for the camera overlay.
nibContents = [[NSBundle mainBundle] loadNibNamed:#"CameraController" owner:self options:nil];
[nibContents retain];
photoPicker.cameraOverlayView = overlay;
// More initialization code here...
}
return self;
}
The only way I can get the elements to load from the CameraController.xib file is to call loadNibNamed:owner:options:. Otherwise the camera takes over but no overlay nor other view components are displayed. It appears that a side-effect of this problem is that none of the view management methods on CameraController are ever called, like viewDidLoad, viewDidAppear etc.
However, all outlets defined in the nib seem to be working. For example, when the camera loads a view is displayed with some instructions for the user. On this view is a button to dismiss it. The button is declared in CameraController along with the method that is called that dismisses this instructions view. It is all wired together through the nib and works great. Furthermore, the button to take a picture is on the view that servers as photoPicker's overlay. This button and the method that is called when it's pressed is managed by CameraController and all wired up in the nib. It works fine too.
So what am I missing? Why can't I use UIViewController's default initializer to create the CameraController instance. And, why are none of CameraController's view mangement methods ever called.
Thanks.
Your problem is easy but need some steps.
Well... First, if overlay is an IBOutlet, it can not be loaded at init time. So move picker and co in viewDidLoad. Place also here all other items that your say that they are not loaded. They should be loaded there (viewDIDLoad). Check that outlets are connected.
Second, call
cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
bundle:nil];
and ensure that CameraController contains (just) a view, and CameraController inherits UIViewController. Check also file's owner.
And at some time, you may consider that calling :
[self presentModalViewController:cameraController.photoPicker animated:NO];
does not make the CameraController control your picker. Does that make sense to you ?
What does that do regarding your problem ?
It seems you are confusing some things. I try to explain in another way :
The one that controls the picker is the one that is its delegate. Your may consider creating in a MAIN view.
The controller of the overlay (added as subview) is the one that own its view in File's Owner. That may be created from the MAIN view, adding its view as subview of the controller. Basically, it is loaded just to get the overlay, but viewDidLoad, ... won't be called.
That's all and I belive those steps are not ok in your code.
That should give something like :
MainController
Loadcamera {
self.picker = [UIImagePicker alloc] init.....];
self.picker.delegate = self;
SecondController* scnd = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
[self.picker addOverlay:scnd.view];
[self presentModalViewController:self.picker animated:NO];
}
/// And here manage your picker delegate methods
SecondController
// Here manage your IBActions and whatever you want for the overlay

Using setViewController from UINavigationController on iPhone doesn't behave properly

I'm having a problem with an iPhone App using UINavigationController. When I'm using pushNavigationController, it works fine. The iPhone does its animation while switching to the next ViewController. But when using an array of ViewControllers and the setViewControllers method, it has a glitch in the animation which can grow into a clearly visible animation bug.
The following snippet is called in the root ViewController. Depending on a condition it should either switch to ViewController1, or it should directly go to ViewController2. In the latter case the user can navigate back to vc1, then to the root.
NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack
[viewControllers addObject:self];
// add first VC
AuthentificationViewController* authentificationViewController =
[[[AuthentificationViewController alloc] initWithNibName:#"AuthentificationViewController" bundle:nil] autorelease];
[viewControllers addObject:authentificationViewController];
if (someCondition == YES)
{
UserAssignmentsListViewController* userAssignmentsListViewController =
[[[UserAssignmentsListViewController alloc] initWithNibName:#"UserAssignmentsOverviewViewController" bundle:nil] autorelease];
[viewControllers addObject:userAssignmentsListViewController];
}
[self.navigationController
setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];
As you can see I'll add the first and maybe the second VC to the array, finally setting the navigationController stack with animation. This works properly if I only add the first controller. But in the case where the animation should go to the 2nd controller, the navigation bar's title won't be "flying in". Instead there is an empty title until the animation is finished. And, even worse, if I replace the navbar title with a custom button, this button will be displayed in the upper left corner until the animation is finished. That's quite a displaying bug.
I tried to use a workaround with multiple pushViewController methods, but the animation doesn't look / feel right. I want the navigation to do its animation in the same way as pushViewController does. The only difference here is, that I don't add a VC but set the whole stack at once. Is there another workaround here, or could this be considered as a framework's bug? I thought about using only pushNavController for VC2, then somehow insert VC1 into the stack, but that doesn't seem possible.
Thanks for all hints and advices. :-)
Technical data: I'm using iOS 4.2, compiling for 4.0.
Finally I found the solution. The mistake was that the new top-level NavigationController has not been initialized and loaded properly until the animation is done. In my case, UserAssignmentsListViewController has a viewDidLoad method that will not be called until animation is done, but it sets the navigation title (here: a UIButton). Therefore the animation fails.
The solution is to refer to an already initialized view controller when it comes to pushing it to the stack. So initialize our top-level VC somewhere:
// initialize our top-level controller
ViewController* viewController2 = [[[ViewController alloc]
initWithNibName:#"ViewController" bundle:nil] autorelease];
Then when pushing two or more VCs to the stack, the top level one is already initialized and the animation works (following the example from my original question):
NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack, too
[viewControllers addObject:self];
ViewController* viewController1 = [[[ViewController alloc]
initWithNibName:#"ViewController" bundle:nil] autorelease];
[viewControllers addObject:viewController1];
if (someCondition == YES)
{
[viewControllers addObject:viewController2];
}
[self.navigationController
setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];

Why is this iPhone program not calling -loadView?

I am trying to work my way through basic iPhone programming and I have a good basic understanding of how Interface Builder works, so I decided to try my hand at doing the views programmatically. I have gone through the ViewController Apple guide and searched everywhere and I cannot seem to find a solution to my problem. This leads me to believe it is a very simple solution, but I am just really banging my head against the wall. Basically all I am trying to do is create a view that gets main window as a subview. I know that if self.view is not defined then the loadView method is supposed to be called, and everything is supposed to be set up there. Here is the current state of my code:
The delegate:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
StartMenuViewController *aViewController = [[StartMenuViewController alloc] init];
self.myViewController = aViewController;
[aViewController release];
UIView *controllersView = [myViewController view];
window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
[window setBackgroundColor:[UIColor redColor]];
[window addSubview:controllersView];
[window makeKeyAndVisible];
}
The view controller:
- (id)init {
if (self = [super init]) {
self.title = #"Start Menu";
}
return self;
}
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
UIView *startView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
[startView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[startView setBackgroundColor:[UIColor greenColor]];
self.view = startView;
[startView release];
}
Thanks for the help in advance!
Are you sure that you're inheriting from UIViewController and not overriding the implementation of - (UIView*)view?
EDIT: More info:
UIViewController has a special implementation of the "-(UIView*) view" message so that when it's called, the loadView method is called if the view member variable is not set. So, if you provide an implementation of "- (id)view" in your subclass, (or a property named view) it will break the auto-calling of "- loadView".
Just to document a "loadView is not called" case:
I wrote a 2 UITableViewController(s) to handle detail data for a master ViewController. Since the devil was in #2, I made a simple UITableViewController for #1, and referenced it in the XIB for the "master" ViewController.
When I was done with #2, I could simply copy the code to #1, remove the complicated code, and go on with life.
But to my dismay and several days work, no matter what I did, viewLoad was not being called for my simple #1 UITableViewController.
Today I finally realised that I was referencing the UITableViewController in the XIB to the master ViewController program. - and of course, loadView was never being called.
Just to help some other dork that makes the same mistake....
Best Regards,
Charles
viewDidLoad only if the view is unarchived from a nib, method is invoked after view is set.
loadView only invoked when the view proberty is nil. use when creating views programmatically. default: create a UIView object with no subviews.
(void)loadView {
UIView *view = [[UIView alloc] initWithFrame:[UIScreen
mainScreen].applicationFrame];
[view setBackgroundColor:_color];
self.view = view;
[view release];
}
By implementing the loadView method, you hook into the default memory management behavior. If memory is low, a view controller may receive the didReceiveMemoryWarning message. The default implementation checks to see if the view is in use. If its view is not in the view hierarchy and the view controller implements the loadView method, its view is released. Later when the view is needed, the loadView method is invoked
again to create the view.
I would strongly recommend you use interface builder for at least your initial Window/View.
If you create a new project in XCode you should be able to select from one of many pre-defined iPhone templates that come with everything setup.
Unless I am reading this wrong, you did not associate any view with the the controller's view property like this
myViewController.view = controllersView;
So as far as Cocoa is concerned the view you are setting in the window has no controller to call loadView on. loadView is a View controller, not view, method. The view you assign to the window is not associated with any view controller. So your view controller loadView method is never called. Get it? The view you are trying to display, has no view controller associated with it.
When you use interface builder to create views you can link the UIView object you created in IB to the view property in the controller in IB which the framework automatically
But if not done in IB you have to set it