I'm not understanding why this is giving me a EXC_BAD_ACCESS. Basic background, im collecting and processing some information, and then using a modal view to let the user confirm if he want to continue.
I have a button on the navigation bar called continue, that calls my data prepare function.
- (void)viewDidLoad {
//Other stuff
UIBarButtonItem *next = [[UIBarButtonItem alloc]
initWithTitle:#"Next"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(prepData)];
self.navigationItem.rightBarButtonItem = next;
[next release];
[super viewDidLoad];
}
prepData:
-(void)prepData{
/*
There's a bunch of stuff going on here, if "mensaje" is not an empty NSString, there is some kind of error that wont let me go on, if not, everything in the data is fine
*/
if(![mensaje isEqualToString:#""]){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:nil
message:mensaje
delegate:nil
cancelButtonTitle:#"Aceptar"
otherButtonTitles:nil];
[alert show];
[alert release];
}else{
UIActionSheet *actionSheet = [[UIActionSheet alloc]
initWithTitle:#"¿Esta seguro que desea realizar estas operaciones?"
delegate:self
cancelButtonTitle:#"Cancelar"
destructiveButtonTitle:#"Aceptar"
otherButtonTitles:nil];
[actionSheet showInView:self.view];
[actionSheet release];
}
}
If I debug, I can get all the way through the prepData(), as soon as I press continue I get a EXC_BAD_ACCESS. If I comment out the [actionSheet release]; I dont get the exception, but it is to my knowledge, that just like alert views, action sheets "stick around" until they show.
At least all books I've read state that, but its quite possible that I'm not understanding something in the autoreleasing.
Just for reference, the alert pops up just fine.
Anybody have any idea whats going on here?
Thanks, Stefano.
Edit: Figured it out, the above code for actions sheets and alert views is fine, the issue was that I was releasing something that was later trying to be autoreleased.
I have for loop that does this:
for(someConditions){
NSString *montoFormateado = [[[NSString alloc] initWithFormat:#"%.2lf",[monto doubleValue]] stringByReplacingOccurrencesOfString:#"." withString:#","];
[_postBuild setObject:[NSString stringWithString:montoFormateado] forKey:[NSString stringWithString:iidvar]];
[montoFormateado release];
}
postBuild = [_postBuild mutableCopy];
[_postBuild release];
Now, it seems the error was in the fact that by using [NSString strintWithString:montoFormateado] I was leaving that string up for autorelease later, but when I released _postBuild that string was getting released also, I removed that and just used setObject:montoFormateado and its working fine.
Its leaking memory, but I think thats for a diferent question, the exc_bad_access got solved.
The easiest way to find this kind of bug is to use the NSZombieEnabled environment variable.
When you get an EXC_BAD_ACCESS at the end of a call, it usually means that something is being autoreleased, but it's retain count is already 0 from you releasing it.
Check this post, it'll save your life a lot of times. If that doesn't work, leave a comment and we'll put our debugging caps back on :)
I think there's one release too many in the code you didn't show (in the "There's a bunch of stuff going on here, if "mensaje" is not an empty NSString" block).
There's nothing wrong with your actionSheet code. As you say, it should deallocate itself once it's no longer on screen. But if you over-released one of the sub-views of self.view then it could cause a crash when the autorelease pool is drained.
In both cases, if you're gonna "release", you better "retain" first.
In the case of the UIActionSheet - I believe the view passed into showInView is retaining a copy of the actionsheet.
In the AlertView this is not the case.
Maybe you are releasing the UIAlertView before its used, try removing the release statement and declare the UIAlertView as auto release (though it should be fine the way you have it)
if(![mensaje isEqualToString:#""]){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:nil
message:mensaje
delegate:nil
cancelButtonTitle:#"Aceptar"
otherButtonTitles:nil] autorelease];
[alert show];
Another thing that it might be is that you are releasing these guys again on some call back or something, you should check you arent doing that, and like the poster above me said, you can use zombies to figure this stuff out as well.
Related
I have a class called FlightRecorder and BookmarkTableViewController. Now, I am presenting the BookmarkTableViewController. And now I am dismissing that view controller. Here is what the code looks like:
[self dismissViewControllerAnimated:YES completion:^{
FlightRecorder *fl = [[FlightRecorder alloc] init];
[fl endBookmarkProcessWithBookmarkCollection: dict];
}
This is getting called after selection of a table view cell. So, FlightRecorder has a method called endBookmarkProcessWithBookmarkCollection: where you pass on an NSDictionary which looks like this:
- (void)endBookmarkProcessWithBookmarkCollection: (NSDictionary *)dict {
NSString *compiledText = #"Random Airline";
NSString *string = [NSString stringWithFormat: #"\nMiles: %.2f\nFlight: %#\nDate: %#", [[dict objectForKey: #"miles"] floatValue], compiledText, [[NSUserDefaults standardUserDefaults] objectForKey:#"tempD"]];
self.bkBookmarkAlertView = [[UIAlertView alloc] initWithTitle:#"Confirmation" message:string delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Add", nil];
[bkBookmarkAlertView show];
}
So as you can see, I use the dictionary and some other variables to piece together the message of an alert view! The alert view has two buttons, Cancel and Add. But the problem is that when you press either of them, they both crash, and the clickedButtonAtIndex: delegate method is not called. I have not had this experience with any other alert views in the exact same view controller.
I looked at some other questions of similar manner, but they're solutions were using ARC. Bottomline they said that self, the delegate of the alert view, is nil or overreleased or something like that. So I thought, maybe that method is getting called before the view loads, even though it is called on apparent completion (the completion block in BookmarksTableViewController).
So I decided to put NSLog's in both viewDidLoad and endBookmarkProcessWithBookmarkCollection:, but viewDidLoad is getting called first, so I am not sure where the problem is.
Thanks for any help!
That block has finished executing and f1 has gone out of scope and been deallocated well before you tap the buttons. I'm not familiar with the architecture of your app but you'll need to find an owner for that object while waiting for the tap.
For some reason screen gets dark and freezes, alert is not shown... can someone please help?
Thanks in advance!
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Hello!"
message:#"Hello!" delegate:self
cancelButtonTitle:#"Done"
otherButtonTitles:nil];
[alert show];
[alert release];
}
You are probably calling show from a background thread, call it on the main thread like this:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Hello!"
message:#"Hello!" delegate:self
cancelButtonTitle:#"Done"
otherButtonTitles:nil];
[alert performSelectorOnMainThread:#selector(show)
withObject:nil
waitUntilDone:NO];
[alert release];
Delegate is correct, but maybe because your do a release at the end it may cause a problem.
Try with a nil delegate :-)
For example :
UIAlertView *alertView;
alertView = [ [ UIAlertView alloc ] init ];
[ alertView setMessage:#"Hello World" ];
[ alertView show ];
[ alertView release ];
If it works, then it was the delegate and you need to declare the variable as a class var. Or it maybe be elsewhere.
Is this alert maybe sitting in a big loop and you are not running on multiple threads? The screen darkening and nothing happening is something I equate with running a long process on the main thread (so the UI doesn't refresh and show the alert).
You get a dark screen without a popup, or slower popup if you show the UIAlertView from a background thread. Just out it back in the main thread and it will be fine. I just had this problem last week.
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];
I create a class:
#interface myUITableViewController : UIViewController
{
NSArray *listData;
}
...
and later,I do so:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
myUITableViewController *myUITable = [[myUITableViewController alloc] init];
[alert addSubview:myUITable.view];
[alert show];
After running,the result is that the myUITable.view's size is bigger than alert.
Why?
Please tell me if you know.
Thank you!
UIAlert is not really meant to be used like that. You should make your own custom UIView and add whatever content you need in it (the table and buttons). Then handle how it shows and hide yourself.
Even if you manage to get it shown correctly chances are it might break in the future. In one of my apps i was showing an alert with a UITextField. I was making space for it by adding "\n" to the message. In later iOS versions this stopped working and it looked really awful...
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.