Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dismissing Popover, [UIPopoverController dealloc] reached while popover is still visible

I have a UIPopoverController stored in a strong property in my View Controller. When the user rotates the iPad while the popover is visible, I dismiss the popover and set my property to nil.

if (self.popover != nil) {
    [self.popover dismissPopoverAnimated:NO];
    self.popover.delegate = nil;
    self.popover = nil;
}

When the code gets to self.popover = nil, ARC attempts to dealloc the UIPopoverController, but it crashes because it is supposedly still visible.

How am I supposed to dismiss and nil out the popover without it crashing?

like image 834
Kenny Wyland Avatar asked Jan 27 '13 21:01

Kenny Wyland


3 Answers

First off, it would be advisable to check if the popover is being presented, this will conveniently also check if it is allocated:

if ([self.popover isPopoverVisible]) {
    [self.popover dismissPopoverAnimated:NO];
}

Now, the issue is, you don't get the delegate callback - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController if you dismiss the popover programmatically like this, but you need a strong reference to the popover until it is no longer visible.

The way to do this is delay setting the property to nil until you return to the main run loop, as when you get back to the main run loop, all animations will have finished and thus the popover will no longer be visible.

You will want to move the code setting the popover to nil into another method:

- (void)releasePopover {
    self.popover.delegate = nil;
    self.popover = nil;
}

Then, in your rotation callback, add this method to fire on the main run loop, I like to do this by adding an invocation operation to the main run loop:

if ([self.popover isPopoverVisible]){
    [self.popover dismissPopoverAnimated:NO];
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(releasePopover) object:nil];
    [[NSOperationQueue mainQueue] addOperation:invocationOperation];
}

Finally, for the sake of cleanliness, you will probably want to call -releasePopover from inside your - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController callback.

So, putting it all together:

- (void)releasePopover
{
    self.popover.delegate = nil;
    self.popover = nil;
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    if ([self.popover isPopoverVisible]){
        [self.popover dismissPopoverAnimated:NO];
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(releasePopover) object:nil];
        [[NSOperationQueue mainQueue] addOperation:invocationOperation];
    }
}

- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
    [self releasePopover];
}

Having said all that, unless there is a good reason, you may just want to keep the popover around to reuse and only set it to nil when you get low-memory warnings and/or if your view is unloaded, as Chris Loonam's answer mentioned

like image 58
Simon Goldeen Avatar answered Nov 16 '22 12:11

Simon Goldeen


Try to nil it out in the viewDidUnload, if you really feel it necessary to do so. Since ARC automatically releases objects, I'm not sure if doing this is really necessary.

like image 22
Chris Loonam Avatar answered Nov 16 '22 13:11

Chris Loonam


Standing on the shoulders of Simon's answer, here's my fix for the crash:

// set to nil on main queue to prevent "dealloc'd while still visible" exception
dispatch_async(dispatch_get_main_queue(), ^{
        self.popover = nil;
});
like image 21
funroll Avatar answered Nov 16 '22 13:11

funroll