UIPopover on ARC - objective-c

I am using ARC on an iPad app with the code below, the popover flashes on the screen, but doesn't stay.
What I am doing wrong?
Please help
- (IBAction)photoLibraryAction:(id)sender
{
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
[imagePicker setDelegate:self];
UIPopoverController *pop1 = [[UIPopoverController alloc] initWithContentViewController:imagePicker];
[pop1 setDelegate:self];
[pop1 presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[pop1 setPopoverContentSize:CGSizeMake(320, 400)];
}
if ([pop1 isPopoverVisible])
{
// Popover is not visible
[pop1 dismissPopoverAnimated:YES];
}
}

In ARC, pop1 will be released right after -photoLibraryAction: returns, because ARC doesn't know that -presentPopoverFromBarButtonItem:permittedArrowDirections: makes the object usable beyond its scope.
You'll have to add an instance variable for your popover controller so ARC doesn't release it. Your if-statement is invalid, too, because when the method returns, pop1 is no longer available for you to use. You'll have to use an instance variable there as well.

Related

Memory Leak in initWithContentRect and setContentView

In my application, I am creating a window and setting a content view. Inspector is showing me some memory leaks.
In setContentView: I think it releases previous NSView.
My code is given below:
//contentrect is NSRect which is already initialized.
//stylemask is NSUInteger which is also initialized.
//window_ is of NSWindow type.
window_ = [NSWindow alloc];
//Here, I am getting memory leak.
window_ = [window_ initWithContentRect:contentrect styleMask:stylemask
backing:NSBackingStoreBuffered defer:NO];
//Set window delegate to receive close notication.
[window_ setDelegate:delegate_];
//I believe this is the behavior is by default.
//[window_ setReleasedWhenClosed:YES];
//Setting the windows title.
[window_ setTitle:title_];
//Setting the window frame screenrect which is also initialized.
[window_ setFrame:screenrect display:YES animate:YES];
//MyView is inherited from NSView.
//Set MyView instead of default NSView.
//Set as it have same content rectangle.
contentrect = [[window_ contentView] frame];
//Allocate MyView.
MyView * view = [[MyView alloc] initWithFrame:contentrect];
[window_ setContentView:view];
[rWindow orderFront:nil];
Edit:
I am not getting memory leak in setContentView which I called, but in setContentView which is called inside as initWithContentRect.
You have to balance retain/release count
you have to release window_ somewhere, one place will be in dealloc
- (void)dealloc
{
[window_ release]; window_ = nil;
// other releases
[super dealloc];
}
it is a bad idea to alloc init in different line
similarly, you have to release your view by add [view release]; after setContentView
or autorelease it
MyView * view = [[[MyView alloc] initWithFrame:contentrect] autorelease];
if you have no idea how management works in Objective-C, you better switch to ARC first and learn details about how it works.

Showing UIImagePickerController in UIPopoverController with existing UINavigationController (adding back button)

So I have a UIPopoverController what houses my UINavigationController where I have my UITableViewController however one of my options on the UITableView is to go and select an image with the UIImagePickerController... Now on the iPhone I can simply use presentModalViewController:animated: however calling that from within a UIPopoverController causes a crash so thats not possible...
I also know the UIImagePickerController needs its own UINavigationController so I can't just push pushViewController:animated: either...
So I figured out that if I keep a link to the UIPopoverController I created, I can then use setContentViewController:animated: to switch to the UIImagePickerController's viewController...
However, I am now stuck at giving the user a way to go back to the previous UINavigationController as I need to be able to add a cancel button to the UIImagePickerController but when I try to do this the cancel button won't get added...
Heres my code that i'm using
-(void)doPhotoalbums {
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
[imagePicker setDelegate:self];
[imagePicker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
[imagePicker setContentSizeForViewInPopover:CGSizeMake(320, 480)];
UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:nil];
[imagePicker.navigationItem setLeftBarButtonItem:cancel];
//[self presentModalViewController:imagePicker animated:YES];
[[self parentPopoverController] setContentViewController:imagePicker animated:YES];
} else {
[UIAlertView showMessage:#"This device does not have any photo albums."];
}
}
So my question is.. Does anybody know how I can get around this? either by adding a cancel/back button what I can hook up to make the navigationControllers switch back or another way to present this (i'd like to avoid switching between two UIPopoverControllers but I don't know what else I can do..
Thanks
Liam
Ahh.. after a little break I found this: https://discussions.apple.com/thread/1710435?start=0&tstart=0
using the UINavigationControllerDelegate you can use the navigationController:willShowViewController:animated: method to access the navigationBar.. then with some code (below) you can add a button.
if ([navigationController isKindOfClass:[UIImagePickerController class]]) {
UINavigationBar *bar = navigationController.navigationBar;
UINavigationItem *top = bar.topItem;
UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(imagePickerControllerDidCancel:)];
[top setLeftBarButtonItem:cancel];
} else {
//do non imagePickerController things
}

Two UIPopovers in One View

I am trying to put two different UIPopovers in one view. I'm fairly new to objective-c, and programming in general, so instead of doing the smart, efficient method of having one popover and changing it's contents depending on how it is called, I just used the stupid, simple method of just creating two views, two delegates, two popovers etc... I don't know if that's why I'm having a problem, or if it's for some other reason.
So here's the problem. In the viewdidload of the view where the popovers appear, I have this code:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
optionsViewController =[[OptionsViewController alloc]init];
optionsViewController.delegate = self;
popoverController = [[UIPopoverController alloc] initWithContentViewController:optionsViewController];
popoverController.popoverContentSize = CGSizeMake(320, 216);
[popoverController setDelegate:self];
newCurrencyViewController =[[newCurrencyViewController alloc]init];
newCurrencyViewController.delegate = self;
newCurrencyPopoverController = [[UIPopoverController alloc] initWithContentViewController:newCurrencyViewController];
newCurrencyPopoverController.popoverContentSize = CGSizeMake(320, 216);
[newCurrencyPopoverController setDelegate:self];
}
Obviously optionsViewController is the vc that appears inside popover 1 (with popover controller called "popoverController"), and newCurrencyViewController is the vc that appears inside popover 2 (with popover controller called "newCurrencyPopoverController").
Every time the view loads, the app crashes with a SIGABRT error, and the console says:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIPopoverController initWithContentViewController:] must not be called with `nil`.'
Also, there's a warning saying "instance method -alloc not found (return type defaults to id)" for the line that
saysnewCurrencyViewController =[[newCurrencyViewController alloc]init];
My first thought was that I had misspelled the name of a file somewhere, since I think the problem is that it isn't finding the file called newCurrencyPopoverController, but I've checked everything and can't find any misspellings or anything. Any ideas?
Thanks very much!
LUKE
You are calling methods alloc + init of your variable newCurrencyViewController but you should call them to the class of that variable!
Line with bug:
newCurrencyViewController =[[newCurrencyViewController alloc]init];
The result of this line will be newCurrencyViewController == nil. And when you will try to init UIPopoverController with that view it will crash as you described.
If variable newCurrencyViewController is of class, for example, CurrencyViewController then you should replace that line with this one:
newCurrencyViewController =[[CurrencyViewController alloc] init];
you dont have an object to call alloc and init on when you are calling newCurrenceViewController.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
optionsViewController =[[OptionsViewController alloc]init];
optionsViewController.delegate = self;
popoverController = [[UIPopoverController alloc] initWithContentViewController:optionsViewController];
popoverController.popoverContentSize = CGSizeMake(320, 216);
[popoverController setDelegate:self];
//Here is your problem---------------------------------------------
newCurrencyViewController =[[newCurrencyViewController alloc]init];
//-----------------------------------------------------------------
newCurrencyViewController.delegate = self;
newCurrencyPopoverController = [[UIPopoverController alloc] initWithContentViewController:newCurrencyViewController];
newCurrencyPopoverController.popoverContentSize = CGSizeMake(320, 216);
[newCurrencyPopoverController setDelegate:self];
}
you probably want something more like
newCurrencyViewController = [[UICurrencyViewController alloc] init];
Or w/e the name of your custom view controller is

Memory Crash in UIPopoverController

I've now invested days in trying to figure out what is going on and for the life of me I can't see what I am doing wrong. I am popping up a UIPopover when the user touches a point on the screen. The popover has a tab controller and table view that displays information about that point. But when the popover is dismissed, it crashes claiming that:
-[UIAnimator removeAnimationsForTarget:]: message sent to deallocated instance
Here is the code that loads the view controller:
MyViewController *popView = [[MyViewController alloc] init];
myPop = [[UIPopoverController alloc] initWithContentViewController:pop];
[popView release];
myPop.delegate = self;
[airportPop setPopoverContentSize:popView.view.frame.size];
[airportPop presentPopoverFromRect:CGRectMake(location.x,location.y,1,1) inView:self.mainView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
- (void)dismissPopover {
if( myPop != nil ) {
[myPop dismissPopoverAnimated:YES];
[myPop.delegate popoverControllerDidDismissPopover:airportPop];
}
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
[myPop release];
myPop = nil;
}
The actual MyViewController is just a UIViewController that with (abridged for brevity) init:
- (id)init
{
self = [super init];
//create a newview
self.view = popView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, POPUP_WIDTH, POPUP_HEIGHT)];
[popView release];
topBar = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, POPUP_WIDTH, 30)];
....
[popView addSubview:topBar];
[topBar release];
//create a table view
self.table = [[UITableView alloc] initWithFrame:CGRectMake(0, 30, POPUP_WIDTH, POPUP_HEIGHT-30-49)];
table.delegate = table.dataSource = self;
....
//create a tab bar
tabBar = [[UITabBar alloc] initWithFrame:CGRectMake(0, POPUP_HEIGHT-49, POPUP_WIDTH, 49)];
tabBar.delegate = self;
[popView addSubview:tabBar];
[popView addSubview:table];
[tabBar release];
[table release];
return( self );
}
Dealloc is nothing more than [super dealloc] since everything is essentially owned by the view and the view controller will take care of it. When the myPop is released, in DidDismissPopover, the view is also released, so that seems to work okay. But very soon thereafter, I get the crash.
Do I need to do something special to discard the tab view or table view when the popup dismisses?
I am using an autorelease on the cells in the table, should I stop doing that?
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
Thank you in advance for any help!!! Any ideas at all are greatly appreciated!!
-Kevin
[myPop dismissPopoverAnimated:YES] will continue to access you object even after the method call because you set YES for the animation (there is a timer and other stuff going under the hood to perform the animation for that)
So, instead of immediately releasing the object, you could mark it as autorelease to postpone this action, which actually might solved it or not.
Or postpone the release to a time after that makes tyou sure thta the animation will be finished. You could use GCD for that (if you are using iOS 4+) and as the default time for animation in UIKit is 0.3s, the code bellow should do the trick.
double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[myPop.delegate popoverControllerDidDismissPopover:airportPop];
});
EDIT: You should use this time only for the test propose as it is far from being the right way to release an object.
You should store a pointer to your UIPopover and release it in your class dealloc method.
Add following keys in yor Exectables info->Arguments tab-> enviroment variables
NSZombieEnabled = YES
CFZombie = 5
MallocStackLoggingNoCompact = 1
then when you get crash automatically you get a message
some thing like this
(gdb) continue
2011-06-09 11:46:08.404 test [6842:40b] * -[_NSArrayI
release]:message sent to deallocated instance 0X64a4900
then type
(gdb) info malloc-history 0x64a4900
it will give you complete history.
May be it helps you to find the place.
also you can use where command when you got crash.
The fastest way to avoid waiting for animation to end is to set popoverController.delegate = nil as soon as you dismiss the popup or the Popover Delegate method
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
is called.

Using Instruments to improve memory-management with modal view controllers

I feel like I don't understand something fundamental here. I've been working on memory management in my app while using Instruments to check out live allocations. I have a modal view controller (settingsViewController) that has an image for a background. One thing I noticed was that even after settingsViewController dealloc is called, there still is a live Malloc 520 KB, ImageIO is the responsible library. I'd expect live memory to drop back down after dismissing settingsViewController. Is the UIImageView still hanging around somewhere?
Here is how I load the image in viewDidLoad, as well as dismiss the view controller when I'm finished.
- (void)loadView {
[super loadView];
////// background ////////
UIImageView *background = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"settings_background.png"]];
[self.view addSubview:background];
[background release];
//////////////////////////
}
- (void)viewDidLoad {
[super viewDidLoad];
///////// done button //////////
UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(doneButtonPressed)];
self.navigationItem.leftBarButtonItem = done;
[done release];
////////////////////////////////
}
- (void) doneButtonPressed {
[self dismissModalViewControllerAnimated:YES];
}
- (void)dealloc {
NSLog(#"settingsViewController dealloc"];
[super dealloc];
}
At this point, this is all that is in the settingsViewController, so no need to do anything special in dealloc, right? Here is how I'm showing testViewController in the rootViewController.
- (void) loadSettingsView {
SettingsViewController *settingsViewController = [[SettingsViewController alloc] init];
UINavigationController *settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];
[self presentModalViewController:settingsNavigationController animated:YES];
[settingsViewController release];
[settingsNavigationController release];
}
I'd like to make sure I understand what is going on before moving forward. I have several different modal view controllers, each with a different image as a background. Since each one creates a Malloc 520 KB, I end up using 2-3 MB of precious memory for no good reason. What is holding on to that 520 KB?
When you use the +[UIImage imageNamed:] method, the framework caches the image data for you. That's why you see it hold onto some memory even after your view is released. If you're working in the simulator and you want to see it release that memory, send the simulator a memory warning after you've dismissed your view. The image framework should then release the cached image data.