A UIAlertView is displayed if an error occurs. But in the meantime the view on which the UIAlertView were called has been dismissed (and therefore released). If the user clicks on OK the app crashes because a message to a released instance is sent. This will cause your app crashing:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"test" message:#"test" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
[alertView release];
alertView = nil;
[self.navigationController popViewControllerAnimated:YES];
I thought the UIAlertView is an independent unit. But it seems it isn't. Is there a way how I could avoid the app crashing (except not dismissing the view)?
The delegate is called when the UIAlertView is dismissed, so in your case:
delegate:self
Delegates are not retained, like an object added to an array, or a subview would be. So in your case, when you call:
[self.navigationController popViewControllerAnimated:YES];
self is most likely being released, and when the the user dismisses the alert, self is called, but has been dealloc'd so it no longer exists.
An easy way to check this is to put a logger statement, like NSLog(#"I'm gone"); in self's dealloc method, if it's ran, then you know your self isn't around anymore, and any messages sent to it will cause a crash.
Make the UIAlertView a retained property of your view controller so that you can refer to it in your dealloc, then set the alert view's delegate to nil when the view controller is deallocated.
Be sure to properly release the retained alert view once it's been dismissed and on dealloc.
For instance:
#interface MyViewController : UIViewController <UIAlertViewDelegate> {
UIAlertView *alertView;
}
#property (nonatomic, retain) UIAlertView *alertView;
#implementation MyViewController
#synthesize alertView;
- (void)showAlert {
if (alertView) {
// if for some reason the code can trigger more alertviews before
// the user has dismissed prior alerts, make sure we only let one of
// them actually keep us as a delegate just to keep things simple
// and manageable. if you really need to be able to issue multiple
// alert views and be able to handle their delegate callbacks,
// you'll have to keep them in an array!
[alertView setDelegate:nil];
self.alertView = nil;
}
self.alertView = [[[UIAlertView alloc]
initWithTitle:#"Error"
message:#"Something went wrong"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Retry",nil] autorelease];
[alertView show];
}
- (void)alertView:(UIAlertView *)alertViewParam didDismissWithButtonIndex:(NSInteger)buttonIndex {
self.alertView = nil; // release it
// do something...
}
- (void)dealloc {
[alertView setDelegate:nil]; // this prevents the crash in the event that the alertview is still showing.
self.alertView = nil; // release it
[super dealloc];
}
The downside here is that you will not be able to handle the alert view's callback when the user dismisses it. However, since your controller is already gone/released, presumably you don't need to. If you do, you have to set the alert view's delegate to something that will persist.
If the UIAlertView object is to be usable from anywhere in the app, not just on the current view, then retain it inside something that is available from anywhere in the app, either some persistant root view controller under the entire possible view stack, or the app delegate.
Added:
This top level object can also retain the alert view's delegate until after it's done being needed (after alert view dismissal).
(People might wonder I am late by years in answering this question,but it might help someone else)
I guess your problem lies some where in popping the view controller,you are displaying the alert view and at the same time trying to navigate the user back to a view.I would recommend you to follow a hierarchal approach here i.e.:
First of all declare your alert view as a global object,i.e.
#property(nonatomic,retain) UIAlertView *sampleAlert;
Now write your alert view display code where ever desired,say for instance:
-(IBAction)buttonClicked:(id)sender
{
self.sampleAlert = [[UIAlertView alloc] initWithTitle:#"test" message:#"test" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[sampleAlert show];
[sampleAlert release];
}
Finally try to navigate the user to the desired view when the "Ok" button is pressed,i.e. you need to make use of alertView didDismissWithButtonIndex method,i.e.
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if(alertView == sampleAlert)
{
[self.navigationController popViewControllerAnimated:YES];
}
}
Please note that if you have alert view with multiple buttons,you also need to check for button index for distinguishing actions,i.e. check using
if(alertView == sampleAlert && buttonIndex == 0)
{
//Do your stuff
}
else
{
//Do something else
}
This will definitely avoid application crash,thanks :)
Easier way that worked for me is to hold all the alert views in a Array and when the parent view is deallocated enumerate alertViews array and assign the delegate to nil. This will ensure that the touch event is ignored and app will function.
// ARC world
#property (strong, nonatomic) NSMutableArray *alertViews;
- (void)dealloc
{
[self.alertViews makeObjectsPerformSelector:#selector(setDelegate:) withObject:nil];
}
Make sure you are implementing the UIAlertViewDelegate protocol.
If you don't care about when the alert is dismissed just init with delegate as nil.
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"test" message:#"test" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
Related
I create a class to call UIAlertview show on my screen. I write the UIAlert function in another class. Both these two classes are not my viewController class.
I use this UIAlert, which is a UITextfield inside, to store texts into a plist file.
here is the class to call UIAlert:
#import "Story.h"
#implementation Story
...
+ (void)stage1
{
AlertClass *pointer = [AlertClass new];
[pointer doAlert];
}
here is the class AlertClass.m file:
- (void)doAlert
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"Done" otherButtonTitles:nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
[alert show];
}
//this makes crash!
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
self.storyFlow.text = [alertView textFieldAtIndex:0].text;
}
Before I add UIAlertViewDelegate in the .h and override the method "clickedButtonAtIndex", it works great. However, I need to store some data from the UITextfield inside the alert view. I get crash and don't know the message it responds as following.
Please help me to solve this problem. Thanks.
[crash pic] https://dl.dropbox.com/u/47381923/crash.tiff
do an NSLog on the text you get back from the Alert View to see whether that is the crash or the subsequent 'self.storyFlow.text = ' is causing it. Perhaps self.storyFlow has not been created yet (with alloc/init)
would you please look at that piece of code:
/* This app is a game, the user can click an "abort" button anytime,
* and he/she is therefore asked for confirmation ("really abort game?")
*/
- (IBAction)btnAbortClicked:(id)sender {
UIAlertView *alert = [[UIAlertView alloc] init];
[alert setMessage:#"Really abort game?"];
[alert setDelegate:self];
[alert addButtonWithTitle:#"Yes"];
[alert addButtonWithTitle:#"No"];
[alert show];
[alert release];
}
/* Delegate method (I don't like it, I wish I had modal blocking windows) */
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0)
[self quitGame];
}
/* pop the view controller */
- (void)quitGame {
[self.navigationController popToRootViewControllerAnimated:YES];
}
The problem is simple - but apparently not enough for me to solve. The UIViewController gets popped, but doesn't get deallocated. And the problem is strictly related to the UIAlertView, because if I just call quitGame from btnAbortClicked:, the view controller is popped and immediately deallocated.
Instead, it seems some mysterious entity retains it.
Can you help me? Thanks in advance.
Well, I think that you're still inside the alertView when clickedButtonAtIndex is called. I'd suggest moving to alertView:disDismissWithButtonIndex instead, so that you're called after the alertview disappears.
I have such error: when I click navigationbar.backItemButton I'm showing UIAlertView with two buttons. When I press on any of them application terminates just with EXC_BAD_ACCESS. Method - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex don't called. How can I solve it? Thanx!
//h - file
#interface DetailsTableViewController : UITableViewController <UITextFieldDelegate, UIAlertViewDelegate>
//m - file
- (void)viewWillDisappear:(BOOL)animated
{
//if changes unsaved - alert reask window
if (isDirty)
{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Save changes?"
message:#"Press YES if you want to save changes before exit, NO - other case."
delegate: self
cancelButtonTitle: #"NO"
otherButtonTitles: #"YES", nil];
[message show];
[message autorelease];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex: buttonIndex];
if([title isEqualToString: #"YES"])
{
[self saveBtnUserClick];
}
}
I think the problem is that after you tapped back button your current controller is removed from navigation stack and deallocated, so when alert tries to call its delegate methods it calls them on deallocated object which results in EXC_BAD_ACCESS error. To workaround the problem I see 2 obvious options (although there may be better solutions):
Extra retain your controller somewhere (in previous controller may be), but you need to find way to release it when you're done.
Create your custom button instead of standard "back" and just show alert when it tapped. Then in alert's delegate method pop your current controller from navigation stack.
Try Changing delegate to nil instead of self. It fixed my issue.
Is your view controller implementing the UIAlertViewDelegate? If not, add in you interface declaration before the { starts.
Also try NSLogging inside the clickedButtonAtIndex method and print the buttonIndex values and see the console.
Edit: Reading your post again, I guess you indeed have missed the UIAlertViewDelegate in your interface declaration.
Probably [message autorelease];
is you mistake use
[message release];
Because you have used [[UIAlertView alloc] init.....]; there for you should release the memory.
autorelease is something will work with the structure which memory is compiler dependent or you have not given the memory manually.
Enjoy.
"Try Changing delegate to nil instead of self. It fixed my issue." worked for me. Thanx
I have several places where I need to display an alert and handle the dismiss operation in the same way, namely take them to the same view controller. Instead of duplicating the alert specific code in all those places, I created a separate class like below:
AlertUtility.h:
#interface AlertUtility : NSObject {
}
- (void) displayAlert;
#end
AlertUtility.m:
#implementation AlertUtility {
- (void) displayAlert {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:... message:...
delegate:self cancelButtonTitle:... otherButtonTitles:nil] autorelease];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Create another view controller and display its views
[self release] // Release current object because I'm not releasing it where I create it
}
}
Where I need to use this alert (i.e. in MyViewController), I have the following:
MyViewController.m:
AlertUtility *utility = [[AlertUtility alloc] init];
[utility displayAlert];
As you can see, I am not releasing the utility object here (which I should since I own it), but rather in the didDismissWithButtonIndex method of the AlertUtility class. I am clearly missing something here.
I've tried autoreleasing the utility object but then by the time the didDismissWithButtonIndex method is 'called' on the utility object, that object was already released (due to the autorelease).
I've tried making the displayAlert method static (and calling it with [AlertUtility displayAlert];), but then the didDismissWithButtonIndex is never called.
Should I just release the current object from within the didDismissWithButtonIndex method, like I do now, or is there a way to release it in MyViewController instead (without creating a AlertUtility property for the current class)?
Thanks!
EDIT
Should I use the Singleton pattern instead maybe?
Calling [self release] is a big no-no because no object has information about what other object refer to it. The retention system is intended to make sure that if ObjA needs ObjB, that ObjB will remain in memory until ObjA no longer needs it. If ObjB releases itself, the entire system breaks down.
In this case, the UIAlert itself retains the AlertUtility object as it's delegate so you can release the AlertUtility as soon as your done with it just as you would a UIAlert.
So,this is fine:
AlertUtility *utility = [[AlertUtility alloc] init];
[utility displayAlert];
[utility release]; // released here, but still alive
It's not a problem if the AlertUtility object is still alive past the release. A release doesn't necessarily kill an object. It merely says that the object sending the release message has no further need of the released object. Each retaining object just has the responsibility to balance all retains (including implicit ones like init) with a release. Past that the retaining object has no interest and no responsibility for whether the system keeps released object alive or not.
After all, any single object may be retained by multiple retaining objects each with no knowledge of the other. In this case the AlertUtility is retained once by the ViewController with init. Then it is retained again by the UIAlert as a delegate. (Edit: UIAlert assigns it delegate)When ViewController releases the AlertUtility object, it will remain alive as long as the UIAlert needs it and vice versa.
Since the UIAlert object is retained by the UIWindow when it is displayed this means that the AlertUtility object will remain alive until after the UIAlert is dismissed. (Edit: this is valid only for the view, not the AlertUtility delegate)
You would not normally have a UIAlert delegate whose existence is solely dependent on that of the UIAlertitself. Usually, the UIAlert delegate is the view controller that evokes the alert. That way, you don't have to worry about the delegate dying before it completes the task associated with the alert.
You can use this instead:
void displayAlert(NSString *title, NSString *message)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}
Or just:
#define MY_ALERT(TITLE, MESSAGE) \
[[[[UIAlertView alloc] initWithTitle:TITLE \
message:MESSAGE \
delegate:nil \
cancelButtonTitle:#"OK" \
otherButtonTitles:nil] autorelease] show]
And then write:
MY_ALERT(nil, #"Press, OK!");
Your pattern works, although I agree releasing it "elsewhere" is ugly and I try to avoid this. You could make the following approach:
// AlertUtility.h:
#interface AlertUtility : NSObject {
}
- (void) displayAlert;
#end
// AlertUtility.m:
#implementation AlertUtility {
- (void) displayAlert {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:... message:...
delegate:self cancelButtonTitle:... otherButtonTitles:nil] autorelease];
[alert show];
[self retain]; // explicitly retain this (self) object
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// Create another view controller and display its views
[self release] // Release current object because you retained it earlier here
}
}
And later:
// MyViewController.m:
AlertUtility *utility = [[AlertUtility alloc] init];
[utility displayAlert];
[utility release]; // released here, but still alive
Edit: removed the release on the alert, as it was autoreleased.
My alertview appears twice and requires 2 user clicks to dismiss.
- (void) showAlert: (NSString *) message
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"You chose"
message: message
delegate: self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"ok",nil];
av.tag = SLIDER_ALERT;
[av show];
}
I am then releasing the alertview in the delegate method "alertView: (UIAlertView *) alertView clickedButtonAtIndex: (int) index"
I suspect the problem is that I have built my own view hierarchy programmaticaly. I have one parent view for the viewcontroller. Under that i have 2 other views (parentView -> subview1 and subview2). I've tried to call [self.view addSubview: av] but that does not work. Neither does bringToFrontSubView:
Any help would be much appreciated
Peyman
The Alert code is fine (other than the release, mentioned in the comments).
[av show] is all that's required to show a view. You don't add UIAlertViews as subviews.
Call it after a delay of 0.1 sec [self performSelector:#selector(showAlert:) withObject:nil afterDelay:0.10];
Hope this will solve your problem.
With using autorelease the Alert View seems to be twice or 3 times. And for iOS4 it needs to be autoreleased otherwise it will crash.