Something I run into a lot is not being able to create and destroy a ViewController properly when adding the ViewController.view as a subview not on a navigation controller.
for example:
MyViewController *myViewController = [[MyViewController alloc] init];
[currentView addSubView:myViewController.view];
[myViewController release];
That Works great if it is a controllerless view and there are no UIControls that the user must interact with. But sending messages to the view controller of that view causes EXEC_BAD_ACCESS because they are no longer in memory.
MyViewController *myViewController = [[MyViewController alloc] init];
[currentView addSubView:myViewController.view];
This works when sending messages, however it is a memory leak and is caught by the static analyzer.
Setting it as a property of the current view controller works sometimes. But if I need to create a bunch with an unknown number of MyViewControllers and add them to something like a UIScrollView, that doesn't work either.
for (int i = 0; i < [myViewControllers count]; i++) {
MyViewController *myTmpViewController = [[MyViewController alloc] init];
[myCurrentUIScrollView addSubview:myTmpViewController.view];
[myTmpViewController release];
}
Still gonna crash if myTmpViewController has user interaction or something similar. How does one go about adding this, and releasing it properly?
You can have a NSMutableArray and add the controllers there.
for (int i = 0; i < [myViewControllers count]; i++) {
MyViewController *myTmpViewController = [[MyViewController alloc] init];
[myCurrentUIScrollView addSubview:myTmpViewController.view];
[myControllers addObject:myTmpViewController];
[myTmpViewController release];
}
// ..
- (void) dealloc {
[super dealloc];
[myControllers release];
}
You could store a pointer to the view controller in an ivar and then release it in your dealloc method.
If such a subview has limited 'controlling needs', then you might consider to subclass from UIView and have the view control itself (e.g. Be its own delegate)
Otherwise you need to decide for the most logical 'owner' of these viewcontrollers (often the viewcontroller of the parentview) and make them ivars of their owner.
Related
I'm sure this is some stupid mistake, but i'm trying for the past hour to remove a subview from my superview without any success.
On my first view i'm having
UIViewController *helpView = [[[UIViewController alloc] initWithNibName:#"HelpView" bundle:nil] autorelease];
[self.view addSubview:helpView.view];
And then inside helpView i have a button which is connected to an IBAction called "closeHelp" which just does the following:
- (IBAction) closeHelp{
[self.view removeFromSuperview];
}
But this causes my app to crash with EXC_BAS_ACCESS for some weird reason, even those this is inside the HelpView, meaning self.view should be pointed to the correct subview..
Would appreciate your help
Thank you.
Shai.
As Andreas answered, you are trying to remove self.view from its super/parent view.
You basically need to remove the helpView from its parent view.
so it should be
- (IBAction) closeHelp{
[helpView removeFromSuperview];
}
But we dont know what is "helpView" in the above method. As we dont have any handle for it.
So our code should finally look like this.
#define HELP_VIEW_TAG 101 // Give tag of your choice
HelpView *helpView = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
helpView.view.tag = HELP_VIEW_TAG;
[self.view addSubview:helpView.view];
[helpView release];
- (IBAction) closeHelp{
UIView *helpView = [self.view viewWithTag:HELP_VIEW_TAG];
[helpView removeFromSuperview];
}
The self.view does not point to your subview but the root view which your uiviewcontroller manages. You should probably remove only the last object in the subview stack, not the whole view, because now you are removing the whole help view.
Anyway, why do you not present the viewcontroller modally instead of doing this?
[self presentModalViewController:helpView animated:NO/YES];
helpView. modalTransitionStyle = //One of the constants below
UIModalTransitionStyleCoverVertical
UIModalTransitionStyleFlipHorizontal
UIModalTransitionStyleCrossDissolve
UIModalTransitionStylePartialCurl
Usually I am writing self.modalTransitionStyle = // One of the constants
in the viewcontroller which will be presented modally, instead of spreading the code.
You are initializing helpView as a UIViewController.
Make sure you have #import "HelpView.h" (or whatever the helpView .h file is called) in the .h file of the view controller where you are initializing it.
Then, use this code:
HelpView *helpView = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
[self.view addSubview:helpView.view];
That should fix it.
The easiest solution for me eventually was to just define my XIB's file owner as the same class as the parent controller, meaning the parent controller would control both the parent and the subview, which just makes a lot easier. :)
Declare the help view on calss level.
in.h file
#class HelpView;
..
#interface
{
HelpView *helpView;
}
#property(nonatomic,retain)HelpView* helpView;
In.m file
#import "HelpView"
#synthensize helpView;
now add this Code where you want
helpView = [[HelpView alloc] initWithNibName:#"HelpView" bundle:nil];
helpView.view.tag = HELP_VIEW_TAG;
[self.view addSubview:helpView.view];
- (IBAction) closeHelp{
//UIView *helpView = [self.view viewWithTag:HELP_VIEW_TAG];
[helpView removeFromSuperview];
}
-(void)dealloc
{
[helpView release];
}
I have a modal view that is a Navigation Controller. When one of the rows in its UITableView gets tapped, the correct View Controller for that row should be initialized and pushed onto the Navigation Controller's stack (so that the screen now shows that View Controller). But it's not working. I've been trying to debug it for a while, and it appears that the Navigation Controller's retain count is 0 at the time pushViewController is called. I assume that means it has been deallocated, and that this is the root of the problem. But I can't figure out why.
In the following code, AddSportDelegate.m presents the modal view that contains the necessary Navigation Controller (_addItemNavController) initialized with the necessary AddItemTableViewController. Tapping on one of the rows of the Table View managed by AddItemViewController calls the showAddItemDataView: method of AddSportDelegate, which in turn should push the correct ViewController onto the _addItemNavController stack. But, as I note in a comment in the code, the retain count of _addItemNavController at that moment is 0.
Note: I realize this code has memory leaks. I deleted some release lines for the sake of brevity. I also haven't included the code for the view controller that is supposed to be getting pushed, since it doesn't have anything at the moment beyond a UILabel identifying that it is the right View Controller.
AddItemDelegate.m
#synthesize addItemNavController = _addItemNavController;
- (void)showAddItemViewController:(UIViewController *)viewController
{
_parentVC = viewController;
[_parentVC retain];
tc = [[AddItemTableViewController alloc] init];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonSystemItemDone target:self action:#selector(cancelAdd)];
tc.navigationItem.leftBarButtonItem = cancelButton;
tc.title = #"Select a Category";
_addItemNavController = [[AddItemNavController alloc] initWithRootViewController:tc];
tc.superViewController = _addItemNavController;
[_parentVC.navigationController presentModalViewController:_addItemNavController animated:YES];
}
- (void)showAddItemDataView:(SportCategory *)category
{
[category retain];
UIViewController *vc;
if (category.name == #"Soccer") {
vc = [[AddSoccerDataViewController alloc] init];
}else{
vc = [[AddBaseballDataViewController alloc] init];
}
//retain count already 0
NSLog(#"retain count: %i", [_addItemNavController retainCount]);
[_addItemNavController.navigationController pushViewController:vc animated:YES];
}
AddItemTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
_addItemDelegate = [[AddItemDelegate alloc] init];
SportCategory *soccer = [[SportCategory alloc] initWithCategoryName:#"Soccer"];
SportCategory *baseball = [[SportCategory alloc] initWithCategoryName:#"Baseball"];
_categories = [[NSArray alloc] initWithObjects:soccer,baseball,nil];
[self.tableView reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SportCategory *selectedCategory = [_categories objectAtIndex:[indexPath row]];
[_addItemDelegate showAddItemDataView:selectedCategory];
}
I am going to take a shot at this.
if (category.name == #"Soccer")
I come from a java background, but I know a little objective - c. I thought you can't compare strings with == which would mean your view controller was never created. Maybe try a isEqualToString method.
That is my only thought, I could be wrong. But Best of Luck.
The '==' operator isn't the good way to compare strings, but anyway your code should fall into the else part.
About your question, _addItemNavController must be nil because your NSLog prints 0 for its retain count.
Is the method -(void)showAddItemViewController:(UIViewController *)viewController called somewhere ?
Your view controller doesn't seem to be initialized.
A bit of sleep helped me find the problem. There were actually two:
1) The final line in AddItemDelegate read:
[_addItemNavController.navigationController pushViewController:vc animated:YES];
However, _addItemNavController IS the navigation controller, so the '.navigationController' part needed to be deleted.
2) I also needed to assign tc.addItemDelegate to self in showAddItemViewController.
So I feel like a serious rookie right now, but I have a problem I can't seem to figure out. I have a barebones app, with literally nothing in it except a login screen and a second view containing a tableview. When I add the second view after logging in (I have done this like 4 times before...), the table view goes through its delegates and appears that it's going to load, but something happens. I have enabled my NSZombies, and it appears to be deallocating the new view, right before it appears.
After tracing through it, and building up again piece by piece, it appears to happen after I wire the table to the view as the datasource/delegate in IB. I have set the view as a UITableViewDelegate, and the methods indeed get fired. Does anyone have any idea what might be causing this behavior?
Have you added the 'second'view to an exisitng view using addSubview: or added it to some form of UINavigationController or UITabBarController? When you do this it will automatically increase the retain count and whatever code you have releasing the view won't cause is to be deallocated.
In my AppDelegate application:didFinishLaunchingWithOptions I have something like;
LoginViewController *login = [[LoginViewController alloc] init];
[login setDelegate:self];
loginNavController = [[UINavigationController alloc]
initWithRootViewController:login];
[window addSubview:[loginNavController view]];
And then once login has occured (and succeeded using a protocol/delegate to send the message back to AppDelegate) I call this code;
UIViewController *newView1 = [[UIViewController alloc] init];
UIViewController *newView2 = [[UIViewController alloc] init];
UIViewController *newView3 = [[UIViewController alloc] init];
myTabBarController = [[UITabBarController alloc] init];
myNavController = [[UINavigationController alloc]
initWithRootViewController:newView1];
// nav controller now retaining
[newView1 release];
NSArray *viewControllers = [NSArray arrayWithObjects:myNavController,
newView2,
newView3,
nil];
[myTabBarController setViewControllers:viewControllers animated:YES];
[[myTabBarController view] setFrame:[[UIScreen mainScreen] applicationFrame]];
[window addSubview:[tabBarController view]];
// tab bar controller now retaining
[newView2 release];
[newView3 release];
// remove login from application
[[loginNavController view] removeFromSuperview];
The AppDelegate has the following declared in the header file;
LoginViewController *loginViewController;
UITabBarController *myTabBarController;
UINavigationController *myNavController;
In the dealloc method for the AppDelegate these are released.
This gives me my login page and then when that has processed my views with a top nav all controlled using the bottom tab bar.
Hope this helps in some way.
You have either too many release (or autorelease) calls - or not enough retain calls - in your view loading/transitioning code, but it's impossible to be more specific without seeing that code.
What's probably happening is the autorelease pool is being flushed between your view loading and your view being shown, and that's what's leading the behaviour you describe.
I encountered some strange memory leaks executing following code on iPhone device:
#implementation TestViewController
#synthesize myButton;
- (IBAction)buttonPressed {
ABPeoplePickerNavigationController* selectContactViewController = nil;
selectContactViewController = [[ABPeoplePickerNavigationController alloc] init];
selectContactViewController.peoplePickerDelegate = self;
[self presentModalViewController:selectContactViewController animated:YES];
[selectContactViewController release];
}
Releasing the picker simple done as follows:
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker {
[self dismissModalViewControllerAnimated:YES];
}
Instruments marks "selectContactViewController = [[ABPeoplePickerNavigationController alloc] init];" as leaking. Any idea why?
You might want to construct your Picker control like so:
ABPeoplePickerNavigationController* selectContactViewController = nil;
selectContactViewController = [[[ABPeoplePickerNavigationController alloc] init] autorelease];
selectContactViewController.peoplePickerDelegate = self;
[self presentModalViewController:selectContactViewController animated:YES];
When you present the modal view controller, it will retain the view on its own. That's how it's able to still pass you an instance of the view controller to your delegate. Best bet is to set the view controller to be autoreleased, so when it gets popped from the navigation controller, the NSAutoReleasePool will garbage collect it.
Just a comment - do you use any protocol like UINavigationControllerDelegate in the interface declaration?
I encountered a situation where just referencing this protocol caused a similar leak message.
Someone has posted a similar question to this with no resolution, but also no sample code. So, I thought I would post my problem in detail here.
I have a game with several modes of play, each of these having several options. After playing around with various designs, it seems cleanest to put them into a UITabBarController with three tabs, one for each class of games. I created a new UIVIewController which is loaded from the main menu screen (replacing the main screen) and initializes the UITabBarController as follows:
barController = [[UITabBarController alloc] init];
Games1 *vc1 = [[[Games1 alloc] initWithNibName:#"Games1" bundle:nil] autorelease];
Games2 *vc2 = [[[Games2 alloc] initWithNibName:#"Games2" bundle:nil] autorelease];
Games3 *vc3 = [[[Games3 alloc] initWithNibName:#"Games3" bundle:nil] autorelease];
NSArray* controllers = [NSArray arrayWithObjects:vc3, vc1, vc2, nil];
barController.viewControllers = controllers;
[self.view addSubview:barController.view];
When the user selects a game, I remove the UIViewController from the window and deallocate as follows:
- (void)dealloc {
printf("Games released: barController: %d\n", [barController retainCount]);
[barController.view removeFromSuperview];
barController.viewControllers = 0;
[barController release];
barController = 0;
[super dealloc];
}
The problem I have is that when I rotate the device, I get a crash. If I launch a game mode directly from the main screen and rotate, no crash. I've verified that everything is getting deallocated, and my retain count on the bar controller is 1. Any suggestions on how to eliminate this crash? Thanks!
[Edit] A bit more info:
The barController is defined as:
IBOutlet UITabBarController *barController;
with:
#property (nonatomic, retain) IBOutlet UITabBarController *barController;
It ends up the problem was only peripherally related to the UITabBarController. I was adding and removing UIViewControllers directly to my app window, which has been shown to be causing problems elsewhere. Adding a master UIViewController / UIView and only adding and removing from that fixes everything, although an autorelease instead of a release may have worked as well. See the discussion here:
View Controller being sent a message even though it has been deallocated
The UITabBarController was just causing the problem to happen much more quickly and obviously.
Don't do this:
barController.viewControllers = 0;
In -dealloc you should only remove the UITabBarController's view from its superview and release it.