views:

200

answers:

4

If learned the hard way that you should remove the delegate from an object if the life span of the delegate is shorter than the object. But how do you do this if you don't have a reference to the object anymore?

In my iPhone application I have a view controller vc which performs an asynchronous activity and is displayed as a modal view. A cancel button dismisses the modal view. If an error occurs, an UIAlertView alert is displayed. If the user taps on ok, both alert and the modal view disappear. Therefore vc is set as delegate for alert and implements alertView:didDismissWithButtonIndex:. Something like this:

// UIViewController vc
    ...
    UIAlertView *alert = [[UIAlertView alloc]
                          initWithTitle:@"Error"
                          message:@"Something went wrong"
                          delegate:self
                          cancelButtonTitle:@"OK" 
                          otherButtonTitles:nil];
    self.alertView = alert; // needed to unset alertView.delegate in dealloc
    [alert show];
    [alert release];
    ...
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
        [self dismissModalViewControllerAnimated:YES];
    } 
}

Normally, the alert view blocks all input. Unfortunately, it fails to do so in some edge cases. If the user touches down on the cancel-button before the alert view appears and touches up after the alert view appears, the view is dismissed, but not the alert. vc gets deallocated and if the user then taps "ok" on the alert, the application crashes because a message was sent to the released object.

I solved this by assigning alert to a property of vc so I can set alert.delegate to nil in dealloc. I find this solution to be not very elegant, because I don't really need the reference to alert.

Is there a better approach?

Edit: Added the text in italics as clarification

+1  A: 

From a UI perspective, when an alert view is present, it should demand the user's full attention. Perhaps you might consider disabling the Cancel button (as well as any other visible non-alert-view widgets) when there is an alert view present, and adding a button title to the UIAlertView instance which performs the same task. This would provide a clearer user interface and should also neatly solve your memory issue.

Alex Reynolds
+2  A: 

Although, normally an alert view is presented over non changing content. So if the delegate is alive when the view appears it will likely be alive when it's dismissed. If that's not the case, you have to do exactly what you did, and unset the alert view's delegate manually if you no longer care about it's result.

So you do care about the alertview since you care about it's delegate method. The wrinkle is that the delegate may not apply by the time the alert is dismissed. So you need logic there, and for that logic you need to save a reference to the alert view in question.

In other words, you are doing it right. Although, it may have been helpful if UIAlertView retained it's delegate, but it doesn't seem like it does if it crashes when it's dismissed.

Lastly, I thought alert view blocked all screen input? If not, you can make it truly modal by setting vc.view.userInteractionEnabled = NO when the alert appears and switch it back when it's dismissed. This way the user can't dismiss the controller while the alert view is up. Which sounds a bit more sane to me.

Squeegy
I tried setting userInteractionEnabled = NO but the touchUp on the cancel button is still processed. I'll probably have to live with the reference to alert way.
Daniel Hepper
A: 
-(void)delayedDismiss {
    [self dismissModalViewControllerAnimated:YES];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    [self performSelector:@selector(delayedDismiss) withObject:nil afterDelay:0.0];

}
vaddieg
A: 

If you no longer care about the results of the UIAlertView you should probably also dismiss it in the - (void) viewWillDisappear:(BOOL)animated

Felix