A Tale of Two UIViewControllers - objective-c

So I think I'm going crazy.
I've updated to XCode 4.2 so I'd have the IOS 5 SDK.
I have an app that's been working great until the upgrade. Now, I have a strange problem. It does not work on my IOS 5 iPad 2, nor will it work in the IOS 5 simulator. Works fine in the 4.3 simulator.
For the purposes of this question, I have two classes based on UIViewController. They do not use NIB files. One, called HistoryBrowser, works great. The other, NoteBrowseViewController, constructed along the same lines, does not.
From NoteBrowseView.Controller.h:
#interface NoteBrowseViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate, UIActionSheetDelegate, UISearchDisplayDelegate, UITabBarDelegate, MKMapViewDelegate> {
UITableView* tableView;
... buncha other vars ...
}
#property (retain, nonatomic) UITableView* tableView;
... buncha other properties ...
From NoteBrowseViewController.m:
#synthesize tableView
- (id)initWithEditing:(BOOL)inEditingMode inserting:(BOOL)inserting {
self=[super init];
if (self) {
self.isInserting=inserting;
self.isEditing=inEditingMode;
self.viewIsMap=NO;
self.insertType=defInsertTypeLink;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0],#"viewSortOrder",
[NSNumber numberWithInt:1],#"priorityView",
[NSNumber numberWithBool:NO],#"simpleView",
nil];
[defaults registerDefaults:appDefaults];
self.viewIsSimple=[[NSUserDefaults standardUserDefaults]boolForKey:#"simpleView"];
}
return self;
}
-(void)loadView {
self.view=[[UIView alloc]initWithFrame:[[UIScreen mainScreen] applicationFrame]];
UIButton* aButton;
UIImage* buttonImage;
UIBarButtonItem* spacer=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UISegmentedControl* sc=[[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Alpha",#"Edit", #"View", nil]];
[sc addTarget:self action:#selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
[sc setWidth:55.0 forSegmentAtIndex:0];
[sc setWidth:55.0 forSegmentAtIndex:1];
[sc setWidth:55.0 forSegmentAtIndex:2];
sc.selectedSegmentIndex=[[NSUserDefaults standardUserDefaults] integerForKey:#"viewSortOrder"];
sc.segmentedControlStyle=UISegmentedControlStyleBar;
UISegmentedControl* prisc=[[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Open",#"All", nil]];
[prisc addTarget:self action:#selector(prioritySegmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
[prisc setWidth:55.0 forSegmentAtIndex:0];
[prisc setWidth:55.0 forSegmentAtIndex:1];
prisc.selectedSegmentIndex=[[NSUserDefaults standardUserDefaults] integerForKey:#"priorityView"];
prisc.segmentedControlStyle=UISegmentedControlStyleBar;
UIBarButtonItem* segmentedButton=[[UIBarButtonItem alloc]initWithCustomView:sc];
UIBarButtonItem* prioritySegmentedButton=[[UIBarButtonItem alloc]initWithCustomView:prisc];
buttonImage=[UIImage imageNamed:#"13-plus.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* addButton=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(addButton:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"187-pencil.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* editButton=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(editButton:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"21-circle-east.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* simplify=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(simplify:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"25-circle-west.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* complexify=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(complexify:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"243-globe.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* map=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(mapify:) forControlEvents:UIControlEventTouchUpInside];
buttonImage=[UIImage imageNamed:#"162-receipt.png"];
aButton=[UIButton buttonWithType:UIButtonTypeCustom];
[aButton setImage:buttonImage forState:UIControlStateNormal];
aButton.frame=CGRectMake(0.0, 0.0, buttonImage.size.width, buttonImage.size.height);
UIBarButtonItem* notes=[[UIBarButtonItem alloc]initWithCustomView:aButton];
[aButton addTarget:self action:#selector(showNotes:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem* cancelButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(cancelButtonPressed:)];
self.doneToolbar=[NSArray arrayWithObjects:spacer, cancelButton, nil];
self.toolbar=[[[UIToolbar alloc]init]autorelease];
if(self.isEditing) {
self.complexToolbar=[NSArray arrayWithObjects:simplify, spacer, segmentedButton, prioritySegmentedButton, spacer, cancelButton, nil];
self.simpleToolbar=[NSArray arrayWithObjects:complexify, spacer, cancelButton, nil];
}
else {
self.complexToolbar=[NSArray arrayWithObjects:simplify, map, spacer, segmentedButton, prioritySegmentedButton, spacer, addButton, editButton, cancelButton, nil];
self.simpleToolbar=[NSArray arrayWithObjects:complexify, map, spacer, addButton, editButton, cancelButton, nil];
}
self.mapToolbar=[NSArray arrayWithObjects:notes, spacer, prioritySegmentedButton, spacer, cancelButton, nil];
if (self.viewIsSimple) {
[self.toolbar setItems:self.simpleToolbar animated:YES];
}
else {
[self.toolbar setItems:self.complexToolbar animated:YES];
}
self.toolbar.autoresizingMask=UIViewAutoresizingFlexibleWidth;
[self.toolbar sizeToFit];
CGFloat toolbarHeight = [self.toolbar frame].size.height;
CGRect rootViewBounds=self.view.bounds;
CGFloat rootViewHeight = CGRectGetHeight(rootViewBounds);
CGFloat rootViewWidth = CGRectGetWidth(rootViewBounds);
[self.toolbar setFrame:CGRectMake(0,0, rootViewWidth, toolbarHeight)];
self.mapView=[[[MKMapView alloc]initWithFrame:CGRectMake(0, toolbarHeight, rootViewWidth, rootViewHeight-(self.isInserting?49:0)-toolbarHeight)]autorelease];
self.mapView.delegate=self;
self.mapView.zoomEnabled=YES;
self.mapView.scrollEnabled=YES;
self.mapView.autoresizingMask=UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
self.tableView=[[UITableView alloc]initWithFrame:CGRectMake(0, toolbarHeight, rootViewWidth, rootViewHeight-(self.isInserting?49:0)-toolbarHeight)
style:UITableViewStylePlain];
self.tableView.delegate=self;
self.tableView.dataSource=self;
self.tableView.autoresizingMask=UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UISearchBar* searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 44, rootViewWidth, 44.0)];
searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.tableView.tableHeaderView = searchBar;
self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self];
self.searchDisplayController.delegate = self;
self.searchDisplayController.searchResultsDataSource = self;
self.searchDisplayController.searchResultsDelegate = self;
[self.view addSubview:self.tableView];
[self.view addSubview:self.toolbar];
if (self.isInserting) {
UITabBarItem* item1=[[[UITabBarItem alloc]initWithTitle:#"Link" image:nil tag:defInsertTypeLink]autorelease];
UITabBarItem* item2=[[[UITabBarItem alloc]initWithTitle:#"Contents Later" image:nil tag:defInsertTypeContentsLater]autorelease];
UITabBarItem* item3=[[[UITabBarItem alloc]initWithTitle:#"Contents Now" image:nil tag:defInsertTypeContentsNow]autorelease];
UITabBar* tabbar=[[UITabBar alloc]initWithFrame:CGRectMake(0, rootViewHeight-49, rootViewWidth, 49)];
tabbar.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin;
[tabbar setDelegate:self];
[tabbar setItems:[NSArray arrayWithObjects:item1, item2, item3, nil] animated:YES];
[tabbar setSelectedItem:item1];
[self.view addSubview:tabbar];
}
if(self.viewIsSimple) {
self.contentSizeForViewInPopover = CGSizeMake(200.0, 625.0);
}
else {
self.contentSizeForViewInPopover = CGSizeMake(450.0, 625.0);
}
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.note=nil;
[sc release];
[prisc release];
[addButton release];
[prioritySegmentedButton release];
[cancelButton release];
[segmentedButton release];
[spacer release];
[editButton release];
[map release];
[simplify release];
[complexify release];
}
And finally, the a NoteBrowseViewController is instantiated thusly from another view controller:
self.noteBrowseViewController=[[NoteBrowseViewController alloc]initWithEditing:NO inserting:NO];
self.noteBrowseViewController.delegate=self;
self.popoverController = [[UIPopoverController alloc]initWithContentViewController:self.noteBrowseViewController];
self.popoverController.delegate = self;
[self.popoverController presentPopoverFromRect:((UIButton*)sender).frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
So this is what happens. If I run this in debug, and follow along, as soon as execution gets to this line:
CGRect rootViewBounds=self.view.bounds;
The program crashes with the following errors:
2011-10-20 11:42:02.703 ActionNote3[12332:15803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(0x1eb0052 0x2a60d0a 0x1e9d36e 0x1e9e220 0x1968cb6 0x5153b 0x50e11 0x50879 0x52e4a 0xdcc64e 0x4bcb3 0x72d1 0x1eb1ec9 0xd095c2 0xd0955a 0xdaeb76 0xdaf03f 0xdae2fe 0xd2ea30 0xd2ec56 0xd15384 0xd08aa9 0x20c7fa9 0x1e841c5 0x1de9022 0x1de790a 0x1de6db4 0x1de6ccb 0x20c6879 0x20c693e 0xd06a9b 0x2dbd 0x2d35)
terminate called throwing an exception
By watching things during the debug, I know self.view is being set up properly, and that loadView is being called when it should be. And it's not this line that makes it fail - it's anything that refers to self.view! what???
What really makes me crazy is that I have another subclassed view controller, HistoryBrowser, that is loaded in exactly the same manner... and it works fine.
So, aside from being upset that I cannot figure this out, i'd like to understand:
What's changed in XCode 4.2 (from 4.1) that would cause this, or is
this an IOS 5 issue?
What does this error mean, and what can I do
about it?
EDIT:
So based on suggestions from Abberant's answer below, I:
changed the initwithnib to just be init
removed the loadView method
added the full error message
added more relevant code to the init method
And now, referencing self.view works properly. But things have just gotten stranger.
Execution makes it past the first reference to self.view without error. However, it now halts at:
self.tableView.tableHeaderView = searchBar;
in the init method.
The error message received is eerily similar to the one I was receiving before:
2011-10-20 12:34:04.818 ActionNote3[13487:15803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(0x1eaf052 0x2a5fd0a 0x1e9c36e 0x1e9d220 0x1967cb6 0x50b4b 0x50421 0x4fe89 0x4d757 0x6641 0x1eb0ec9 0xd085c2 0xd0855a 0xdadb76 0xdae03f 0xdad2fe 0xd2da30 0xd2dc56 0xd14384 0xd07aa9 0x20c6fa9 0x1e831c5 0x1de8022 0x1de690a 0x1de5db4 0x1de5ccb 0x20c5879 0x20c593e 0xd05a9b 0x212d 0x20a5)
terminate called throwing an exception
So, if I comment that line out, execution procedes until it reaches this group of lines, where it fails when it attempts to do the performFetch.
NSError *error = nil;
if (![[self currentFetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Debugging into the code for [self currentFetchedResultsController] shows that the fetched results controller is being set up properly and without issue.
The error received here is the same as above. The numbers in the first throw call stack change, but otherwise the error is the same.
So I'm left thinking that I have a larger problem going on here. I just have no idea how to track it down. As I said way up there somewhere, I have another view, constructed in the same manner, that loads fine.
Edit:
So NJones in his answer below suggested following Apple's guidelines by placing the appropriate code in loadView and viewDidLoad. So I did. I moved all view construction out of init and into loadView, and fired up the fetchedResultsController in viewDidLoad.
The app still crashes in the same spot:
self.tableView.tableHeaderView = searchBar
With the same error as noted above.
Thanks!

I would say you should use the loading model apple suggests. This is some of the code generated in xCode when you create a new UIViewController subclass (abridged to fit):
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
-(void)loadView{
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
-(void)viewDidLoad{
[super viewDidLoad];
}
*/
Based on just this, Since you are creating your view programmatically, I would say that most of what you have in your init method belongs in the loadView method.

So I found the answer, and it's not something anyone would have guessed without seeing a lot more code. But, hopefully this answer will help other people who might run into the same thing.
In what has become my loadView method (but was originally in my init method, and still is in the code above), I had the following lines:
UISegmentedControl* sc=[[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:#"Alpha",#"Edit", #"View", nil]];
[sc addTarget:self action:#selector(segmentedControlValueChanged:) forControlEvents:UIControlEventValueChanged];
[sc setWidth:55.0 forSegmentAtIndex:0];
[sc setWidth:55.0 forSegmentAtIndex:1];
[sc setWidth:55.0 forSegmentAtIndex:2];
sc.selectedSegmentIndex=[[NSUserDefaults standardUserDefaults] integerForKey:#"viewSortOrder"];
sc.segmentedControlStyle=UISegmentedControlStyleBar;
segmentedControlValueChanged: would set a string var (and store the index of the selected button in user defaults). The string var was later used to determine sort order when firing up my fetchedresultscontroller.
Prior to IOS 5, when I set sc.selectedSegmentIndex, the method identified in the selector would get fired, and everything would get set up properly. In IOS 5, this is no longer the case, and the string var (setting a sort order) would not get set.
What's interesting is that upon execution this line:
self.tableView.tableHeaderView = searchBar;
would cause the fetchedReultsController to be accessed and, by accessing it, it would be initialized. When it went to be initialized, it would set the sort order and include a null value for the field name, hence the error message, which now makes sense.
So the root cause of the problem was that prior to IOS 5, setting the selectedSegmentIndex on a UISegmentedControl would fire the action associated with the control. Starting with IOS 5, this is no longer the case.
The original cause of the problem, which several people helped me resolve, was that I was not following the viewController load process properly. I am not using NIB files. Apple says that if you dont use a nib file, you have to define loadView to set up the view. If loadView is not defined, self.view is set internally, and viewDidLoad is called. The reason the app crashed at the first reference of self.view, then, is that I had logic in viewDidLoad to set up the fetchedResultsController. When I moved the view setup logic into loadView, references to self.view no longer started up the fetchedresultscontroller.

Been thinking about and looking for information on this for a while, but it might be any number of things. Perhaps the self.view gets called before the loadView somewhere, maybe it's because initWithNibName triggers something you don't want, maybe it's because your view is smaller than the screen/window, which is not advised, maybe the new iOS has a bug in it... With so little of your code, no way to debug and an incomplete error message, I can't see what causes it.
However, I might have a workaround:
How about removing that loadView override so that the viewController makes its own view, then add your mainView to that default view?

The exception says you are adding a nil to an array. I'm think tableViewHeader could be considered an objectAtIndex:0 since it's the first of an array of rows. Try putting this line above it :
NSLog(#"searchBar = %#",searchBar;
self.tableView.tableHeaderView = searchBar;
Edit(1):
Then try these:
NSLog(#"self = %#",self);
NSLog(#"self.tableView = %#",self.tableView);
NSLog(#"self.tableView.tableHeaderView = %#",self.tableView.tableHeaderView);
self.tableView.tableHeaderView = searchBar;
Edit(2):
if ([self.tableView respondsToSelector:#selector(setTableHeaderView:)]){
NSLog(#"tableView does respond");
[self.tableView setTableHeaderView:nil];
}
I don't think this will fix the problem, but it should help hunt down the problem.

Related

OCUnit crashes when creating UITextView

Unit testing is handling out humble-pie by the bucketload here at the moment. I am trying to run tests on my BannerAdViewController-class, but everything comes tumbling down when reaching the following line:
UITextView *buyAppTextView = [[UITextView alloc] initWithFrame:CGRectMake(8, 2, 304, 50)];
The console output is as follows:
-[__NSCFType markupDescription]: unrecognized selector sent to instance 0x1bc2de0
This is the method it reaches before the crash occurs:
-(void)setup{
_bannerFrame = [self frameRectForBannerAd];
_bannerWrapperFrame = [self frameRectForBannerWrapperFrame];
UITextView *buyAppTextView = [[UITextView alloc] initWithFrame:CGRectMake(8, 2, 304, 50)];
[buyAppTextView setText:#"some text"];
[buyAppTextView setTextColor:[UIColor blackColor]];
[buyAppTextView setFont:[UIFont systemFontOfSize:10]];
[buyAppTextView setAutoresizingMask:UIViewContentModeScaleAspectFit];
[buyAppTextView setUserInteractionEnabled:NO];
_placeHolderBanner = [[UIView alloc] initWithFrame:_bannerFrame];
[_placeHolderBanner setBackgroundColor:[UIColor whiteColor]];
[[_placeHolderBanner layer] setBorderColor:[UIColor blackColor].CGColor];
[[_placeHolderBanner layer] setBorderWidth:1.0];
[_placeHolderBanner addSubview: buyAppTextView];
[[self view] setFrame:_bannerWrapperFrame];
[[self view] setBackgroundColor:[UIColor clearColor]];
[[self view] addSubview: _placeHolderBanner];
_iAdBanner = [[ADBannerView alloc] initWithFrame:CGRectZero];
[[self view] addSubview: _iAdBanner];
_iAdBanner.delegate = self;
_iAdBanner.hidden = YES;
}
If I comment out everything to do with the buyAppTextView the tests run fine.
(Yes, the test-rig is linked with UIKit in case you were wondering).
Oh, and the test-class looks like this
#import "NorBannerAdViewTests.h"
#import "NORBannerAdViewController.h"
#implementation NorBannerAdViewTests
NORBannerAdViewController *_adViewController;
- (void)setUp{
[super setUp];
_adViewController = [[NORBannerAdViewController alloc] init];
}
- (void)tearDown{
[super tearDown];
[_adViewController release];
}
-(void) testThatFrameRectForBannerAdWrapperDoesNotReturnZero{
CGRect receivedFrame = [_adViewController frameRectForBannerWrapperFrame];
STAssertFalse((CGRectIsEmpty(receivedFrame)), #"receivedFrame should not be zero");
}
-(void) testThatFrameRectForBannerAdDoesNotReturnZero{
CGRect receivedFrame = [_adViewController frameRectForBannerAd];
STAssertFalse((CGRectIsEmpty(receivedFrame)), #"receivedFrame should not be zero");
}
#end
After extensive googling, and tracking through some related threads I (somewhat surprisingly) managed to solve the problem. The problem stems from the fact that the Test target was not setup when creating the project. As it was based on one of the Cocos2D templates I had to add it myself. Doing so, I didn't hook up the test-target's build settings with the App.
I had to add the following two flags to the test-target's build settings and the tests now run fine:
Bundle Loader: $(BUILT_PRODUCTS_DIR)/AppName.app/AppName
Test Host: $(BUNDLE_LOADER)

back button presentModalViewController

I want to have a back button when I go to another view controller but I have not been able to achieve this. I have tried to set the back button to not hidden with no luck. A little background information, there is no navigation controller being loaded when the app starts, I'am trying to go through one uiviewcontroller to another.
Here is how I have tried to achieve this, if anyone can tell what is wrong... it would be appreciated!
displayViewController *controller = [[displayViewController alloc] initWithNibName:#"displayViewController" bundle:nil];
UINavigationController *aNavController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentModalViewController:aNavController animated:YES];
[aNavController release];
[controller release];
You can put the back button in the viewController in which you want to have the back button
- (void)viewDidLoad
{
// back button
UIImage* image = [UIImage imageNamed:#"back.png"];
CGRect frame = CGRectMake(0, 0, image.size.width, image.size.height);
UIButton *backButton = [[UIButton alloc] initWithFrame:frame];
[backButton setBackgroundImage:image forState:UIControlStateNormal];
[backButton addTarget:self action:#selector(backAction:) forControlEvents:UIControlStateHighlighted];
UIBarButtonItem* backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
self.navigationItem.leftBarButtonItem = backButtonItem;
[backButtonItem release];
[backButton release];
}
-(void) backAction:(id)sender
{
[self.navigationController popViewControllerAnimated:YES];
}

Potential leak detected for UIBarButtonItem which is autoreleased

In the following code xCode's Build & Analyze function detects a
Potential leak of an object allocated on line 165 and stored into 'addButton'.
addButton is a UIBarButtonItem using the category barItemWithImage (which I read about here) which returns an autoreleased object. If I don't retain addButtonItem I get an exception on trying to access a released object.
What am I missing here?
UIBarButtonItem *addButton;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
addButton = [UIBarButtonItem barItemWithImage:[UIImage imageNamed:#"RedPlus.png"] target:self action:#selector(createStoryModal:)];
}else {
addButton = [UIBarButtonItem barItemWithImage:[UIImage imageNamed:#"RedPlusiPhone.png"] target:self action:#selector(createStoryModal:)];
}
[addButton retain];
NSArray* toolbarItems = [NSArray arrayWithObjects:
addButton,
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil],
nil];
[toolbarItems makeObjectsPerformSelector:#selector(release)];
self.toolbarItems = toolbarItems;
The category code:
#implementation UIBarButtonItem(MyCategory)
+ (UIBarButtonItem*)barItemWithImage:(UIImage*)image target:(id)target action:(SEL)action{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:image forState:UIControlStateNormal];
[button setFrame:CGRectMake(0.0, 0.0, image.size.width, image.size.height)];
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
return [[[UIBarButtonItem alloc] initWithCustomView:button] autorelease];
}
#end
the static analyzer is right.
remove this:
[addButton retain];
and this:
[toolbarItems makeObjectsPerformSelector:#selector(release)];
and you're also leaking a UIBarButtonItem.
NSArray retains its elements.
There may be other memory issues elsewhere, but that should remove three of the visible issues/bad forms.
While the item is initially an autoreleased object, when you send it the retain message, you are now an owner of this object, which means you must release it. addButton is not being released right now, so that will cause a leak.
Furthermore, you alloc/init a UIBarButtonItem in your toolBarItems array. You don't release this UIBarButtonItem either.
Since the toolbarItems array will increase retainCount of both addButton and the UIBarButton you alloc/init in the array, it is safe to release both after you declared that array. (Or, for clarity, after you set self.toolbarItems to be that array.)

UILabel text not changing?

Unable to change text in lMenu_time (this is a UILabel) after it was initially set.
The call back is executed, I tested this, but the text won't change.
?? I am passing around the pointer and making adjustments to the UILabel. ??
lMenu_time and numerous others are defined in the header file. (not seen here)
UILabel *lMenu_time;
...
-(void) NewNumber: (UIButton*) btn {
if (btn.tag == 102){
iTime++;
[lbl setText:#"time"];
if(iTime > 20){iTime=1;}
[lMenu_time setText:[NSString stringWithFormat: #"Hold: %d", iTime]];
}
....
}
- (void) menuItem: (UIView*)vMenu menuButton:(UIButton*)bMenu menuLabel: (UILabel*)lMenu menuPosX: (double)posX menuLenX: (double)lenX menuTagNum: (int)tagNum menuText: (NSString*)txtMenu{
bMenu = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[bMenu setFrame:CGRectMake(posX,0,lenX,25)];
[bMenu setTag: tagNum];
[bMenu addTarget:self action:#selector(NewNumber:) forControlEvents:UIControlEventTouchUpInside];
[vMenu addSubview:bMenu];
lMenu = [[[UILabel alloc] initWithFrame:CGRectMake(posX,0,lenX,25)] retain];
[lMenu setBackgroundColor:[UIColor lightGrayColor]];
[lMenu setText:[NSString stringWithFormat: txtMenu]];
[lMenu setFont:[UIFont systemFontOfSize:14 ]];
[lMenu setTextAlignment:UITextAlignmentCenter];
[vMenu addSubview: lMenu];
}
- (void) menuBuild{
pSelf = self;
theString = #"";
UIView *vMenu = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,25)];
[pSelf.view addSubview:vMenu];
[vMenu setBackgroundColor:[UIColor grayColor]];
iTime = 2;
[self menuItem:vMenu menuButton:bMenu_time menuLabel:lMenu_time menuPosX:240+20 menuLenX:60 menuTagNum:102 menuText:[NSString stringWithFormat: #"Hold: %d", iTime]];
...
}
Just before you try to set the next, try adding
NSLog(#"My label is %#",lMenu_time);
Then, if your console outputs "My label is (nil)" you'll know that the problem is that the pointer to lMenu_time isn't being passed around properly.
Did you bind the label object to the controller in Interface builder? If not I would bet this is an retention issue. You do not post the code that builds the UILabel object, so if that is not done in IB, ensure you call retain or it will certainly be out of scope when you try and modify it.

Navigation Control Problem

I am using some methods to load a new .xib and go back to the main menu. However after about five time it crashes by using too much memory. I need to be able to go back to the main menu and to the game many times. Any other methods I should use for the navigation controls.
Main Menu part:
GameViewController* game = [[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:game animated:NO];
Game part to return to main menu:
[self.navigationController popViewControllerAnimated:NO];
Here is the viewdidLoad
{
- (void)viewDidLoad {
[super viewDidLoad];
[self StartTimer];
TotalSeconds = 0;
GameCenterTotalSeconds = 0;
timeSec = 0;
timeMin = 0;
Background = [[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 480, 320)] autorelease];
Background.image = [UIImage imageWithContentsOfFile:[ [ NSBundle mainBundle] pathForResource:#"Background" ofType:#"png"]];
[self.view addSubview:Background];
timeLabel.textColor = [UIColor redColor];
[self.view addSubview:timeLabel];
NumberLabel = [[[UIImageView alloc] initWithFrame:CGRectMake(0, -4, 60, 70)] autorelease];
NumberLabel.image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"Number" ofType:#"png"]];
[self.view addSubview:NumberLabel];
QuestionNumber = [[[UILabel alloc] initWithFrame:CGRectMake(23, 17, 20, 20)] autorelease];
QuestionNumber.text = #"1";
QuestionNumber.textColor = [UIColor redColor];
QuestionNumber.backgroundColor = [UIColor clearColor];
[QuestionNumber setFont:[UIFont fontWithName:#"Marker Felt" size:30]];
[self.view addSubview:QuestionNumber];
numberLives = 1;
appDelegate = (OppositeMoronTestAppDelegate *)[[UIApplication sharedApplication]delegate];
musicButton = [[[UIButton buttonWithType:UIButtonTypeCustom] retain] autorelease];
musicButton.frame = CGRectMake(5, 283, 35, 35);
musicButton.backgroundColor = [UIColor clearColor];
if (appDelegate.shouldPlayMusic == YES) {
UIImage *Image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"MusicOn" ofType:#"png"]];
[musicButton setBackgroundImage:Image forState:UIControlStateNormal];
[musicButton addTarget:self action:#selector(TurnMusicOff) forControlEvents:UIControlEventTouchUpInside];
} else {
UIImage *Image = [UIImage imageWithContentsOfFile:[[ NSBundle mainBundle] pathForResource:#"MusicOff" ofType:#"png"]];
[musicButton setBackgroundImage:Image forState:UIControlStateNormal];
[musicButton addTarget:self action:#selector(TurnMusicOn) forControlEvents:UIControlEventTouchUpInside];
}
[self.view addSubview:musicButton];
[self showQuestion1];
}
}
try autorelease on your view controller:
GameViewController* game = [[[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil] autorelease];
The navigation controller will take ownership of the view controller passed to it, so you don't have to keep a reference to it. But you can't keep allocating GameViewControllers over and over without releasing them. autorelease is useful for that. You could also release it after you've passed it to the navigation controller if you prefer:
GameViewController* game = [[GameViewController alloc initWithNibName:#"GameViewController" bundle:nil];
[self.navigationController pushViewController:game animated:NO];
[game release];
game = nil;
EDIT:
So if you're already releasing the game object, then it must be a memory leak within the GameViewController class itself.
Annything you alloc, copy or retain in your GameViewController class you're supposed to release in the dealloc method (and maybe also in the viewDidUnload method if you're alloc/copy/retaining in the viewDidLoad method).
The iOS Memory Management Programming Guide might be helpful if you want to get into more detail.
If you want to post the relevant code from the GameViewController class, I'm sure someone will be able to help you pin down the memory leak.
You can also try the Leaks tool in Instruments
EDIT 2:
I'm assuming you have several IBOutlets connected to properties in your GameViewController class...
don't know if you're already doing this, but in your viewDidUnload method AND on your dealloc method you have to set all of these IBOutlets properties to nil in order to release them, like so:
- viewDidUnload
{
//... whatever comes before
self.timeLabel = nil;
self.NumberLabel = nil;
//... etc
}
- dealloc
{
//... whatever comes before
self.timeLabel = nil;
self.NumberLabel = nil;
//... etc
[super dealloc];
}
In general, if you have any properties declared with retain, that means that when you set that property the object will be retained. If you set that property to nil, the object that was there will be released for you. So any properties with the retain keyword should be set to nil (or the backing ivar released).