I'v encountered EXC_BAD_ACCESS error in my app. Or to be more specific in one of my classes. It is Custom UIAlertView class. I couldn't catch when it throws EXC_BAD_ACCESS in usage. Sometimes it works great just as expected, and in all suden it craches... Here is whole class
#implementation AlertPassword
int counter = 3;
#synthesize done;
#synthesize alertText;
#synthesize msg;
- (void) showAlert :(NSString*) title
{
if(counter != 3){
if(counter == 1)
{
NSString *msgs = #"Last warning";
msg = msgs;
}
else
{
NSString *msgs = [NSString stringWithFormat:#"WRONG PIN. %d times remaining",counter];
msg = msgs;
}
}
else
{
NSString *msgs = #"Enter your pin";
msg = msgs;
}
UIAlertView * alert = [[UIAlertView alloc] initWithTitle:#"Security" message:msg delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles: nil];
_alert = alert;
_alert.alertViewStyle = UIAlertViewStyleSecureTextInput;
alertText = [_alert textFieldAtIndex:0];
alertText.keyboardType = UIKeyboardTypeNumberPad;
alertText.placeholder = #"Pin";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(controlTextDidChange:)
name:UITextFieldTextDidChangeNotification object:alertText];
[_alert show];
[_alert release];
[[NSNotificationCenter defaultCenter] removeObserver:UITextFieldTextDidChangeNotification];
}
- (void)controlTextDidChange:(NSNotification *)notification {
{
NSString *pin = [[NSUserDefaults standardUserDefaults] stringForKey:#"Pin"];
if ([notification object] == alertText)
{
if (alertText.text.length == pin.length)
{
if(counter != 0)
{
if([alertText.text isEqualToString:pin])
{
[_alert dismissWithClickedButtonIndex:0 animated:NO];
[self.tableViewController openSettings];
counter = 3;
}
else
{
counter--;
[_alert dismissWithClickedButtonIndex:0 animated:NO];
[self showAlert:#""];
}
}
else
{
[_alert dismissWithClickedButtonIndex:0 animated:NO];
[[NSUserDefaults standardUserDefaults] setObject:NULL
forKey:#"Telephone"];
[[NSUserDefaults standardUserDefaults] setObject:NULL
forKey:#"Pin"];
[[NSUserDefaults standardUserDefaults] synchronize];
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"" message:AMLocalizedString(#"EraseData", nil) delegate:nil cancelButtonTitle:AMLocalizedString(#"Ok", nil) otherButtonTitles:nil];
counter = 3;
[av show];
[av release];
}
}
}
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *pincheck = [[NSUserDefaults standardUserDefaults] stringForKey:#"pinCheck"];
if (buttonIndex == 0)
{
if(pincheck.intValue == 1)
{
NSLog(#"app kill");
exit(0);
}
else
{
NSLog(#"dismiss");
}
}
}
#end
Here is where i initialize and use this class.
case 5:
NSLog(#"Add remove");
if (pincheck != NULL && pin != NULL){
if([pincheck isEqualToString:#"0"])
{
AlertPassword *alert = [AlertPassword alloc];
alert.tableViewController = self;
NSString *msg = #"Enter your pin code to access:";
[alert showAlert:msg];
// [alert release];
}
break;
}
else
{
NSLog(#"Is null");
[Menu load2View:self];
}
break;
I though maybe it was because i do not release alert. But adding [alert release]; Made to have EXC_BAD_ACCESS directly after the user tries to enter something. Without [alert release]; it works. But sometimes it craches with EXC_BAD_ACCESS
Also sometimes it gets
2012-11-08 12:11:27.451 kodinisRaktas[2485:19d03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSMallocBlock__ dismissWithClickedButtonIndex:animated:]: unrecognized selector sent to instance 0x947aae0'
But I also have no idea why this happends
Please help, I'm pretty new to objective-c and ios, and I have no idea how to get rid of this, I guess someone with a bit of experience will see whats wrong in my code.
I'v just saw, that EXC_BAD_ACCESS or unrecognized selector throws if you push cancel for 4-5 times or more, and then try to type something.
EXC_BAD_ACCESS is mostly due to bad memory handling. The alert has most likely become an zombie... I would have the alert as a property with strong/retain. You should hold on to it while displaying. Not release after "show".
When you set up the first you can do like this
_alert = [[UIAlertView alloc] initWithTitle:#"Security" message:msg delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles: nil]; // retain count: 1
Note calling "show" will also retain it, but that does not change the fact that you need to too.
[_alert show]; // retain count: 2
Wait for the delegate callback and release it.
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[_alert release], _alert = nil; // retain count in next run will be 0
}
Tip: It may be easier to handle if you use UIAlertView combined with blocks. http://gkoreman.com/blog/2011/02/15/uialertview-with-blocks/
I don't see where you initialize alert, anyway I suppose it's a class field, so retain it.If it's not a class instance variable, make it be so, this way you will always have a pointer to it.
[__NSMallocBlock__ dismissWithClickedButtonIndex:animated:]
You are sending this message to a raw malloc block, so probably alert has been released and points to a memory used for something else, something that's not an objc object.
Try to retain alert and see what happens.
May be you are passing wrong values to this "dismissWithClickedButtonIndex:animated:" method which is not recognizing the value signature please do a double check for that;
the excepted answer does make sense and is likely the cause
but what is this?
[NSNotificationCenter defaultCenter] removeObserver:UITextFieldTextDidChangeNotification];
Related
This question already has answers here:
Two Alert Views in One View Controller - buttonIndex Response
(4 answers)
Closed 7 years ago.
EDIT:
Problem Solved ==> Simply giving a tag solved the problem.
I have the following problem:
On a view I have two UIalertviews:
NSString *message = [NSString stringWithFormat:#"Users must enter this code to join the meeting: %#", meetingCode];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Meeting code"
message:message
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:#"Copy to clipboard", nil];
[alert show];
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == [alertView cancelButtonIndex])
{
NSLog(#"Code not copied");
}
else
{
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = meetingCode;
NSLog(#"Code copied");
}
}
and this one:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
AgendaModel* agenda = _meeting.agenda[indexPath.row] ;
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:agenda.id,#"id",agenda.name,#"name", nil];
NSString *message = [NSString stringWithFormat:#"Are you sure that you want to delete : %#?", agenda.name];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Warning"
message:message
delegate:self
cancelButtonTitle:#"Close"
otherButtonTitles:#"Delete", nil];
[alert show];
NSString *delete_url = [NSString stringWithFormat:#"RestAgendas/delete.json"];
[_meeting.agenda removeObject:agenda];
[self.tableView deleteRowsAtIndexPaths:[NSMutableArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[JSONAPI getWithPath:delete_url andParams:dict completion:^(id json, JSONModelError *err) {
NSLog(#"%#", json);
}];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == [alertView cancelButtonIndex])
{
NSLog(#"Agenda Won't Be Deleted");
}
else
{
NSLog(#"Agenda Will Be Deleted");
}
}
Now is the problem that I got the error: Duplicate declaration of method 'alertView:clickedButtonAtIndex'
How can I fix this? I tried some stuff I found here but I still can't make it work. Can someone help me?
Yep, like you said, giving a tag allows you to have multiple UIAlertView in one single view.
Same goes for UIActionSheet or UIAlertController
for future reference, even though this is probably a duplicate, simply create your alert like usual and add
myAlert.tag = 123; //any number of your choice
and in alertView:clickedButtonAtIndex
you can find it using a switch or some if's
if (alertview.tag = 123){
// this is my alert
}else if(alertview.tag = 333){
// this is my other alert
}
For what it's worth, I suggest using else if and not just if to avoid the whole method to be read everytime, and order your if's by decreasing likeliness of being called.
Note you can also give tags to buttons, views, cells, labels, and just pretty much every outlet you can find.
I tried to dial a number with the phone app after the user click on a button on a UIAlertview. The phone app did open, but the original app crashed right after clicking the button on the UIAlertview. Does anyone one know the reason? I did try to make sure I released everything that should be released. Thanks! Below is the code:
-(IBAction)dialButtonPressed:(UIButton *)numberButton
{
if ([company isEqualToString:#"Not Found"]==true){
message = [[UIAlertView alloc] initWithTitle:#"Sorry"
message:#"No replace number found. Would you like to dial anyway?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
message.tag = 0;
if(phoneLinkString)
{
[phoneLinkString release];
phoneLinkString = nil;
}
[message show];
[message autorelease];
phoneLinkString = [[NSString stringWithFormat:#"tel:%#",replace]retain];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
[self release];
message = nil;
if(message.tag == 0 && buttonIndex == 1){
NSURL *phoneLinkURL = [NSURL URLWithString:phoneLinkString];
[[UIApplication sharedApplication] openURL:phoneLinkURL];
}
- (void)dealloc {
[phoneNumberString release];
[phoneNumberLabel release];
[self release];
[message release];
[super dealloc];
}
The newest code
-(IBAction)dialButtonPressed:(UIButton *)numberButton
{
if ([company isEqualToString:#"Not Found"]==true){
message = [[UIAlertView alloc] initWithTitle:#"Sorry"
message:#"No replace number found. Would you like to dial anyway?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
message.tag = 1;
if(phoneLinkString)
{
[phoneLinkString release];
phoneLinkString = nil;
}
[message show];
[message autorelease];
phoneLinkString = [[NSString stringWithFormat:#"tel:%#",replace]retain];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(message.tag == 1 && buttonIndex == 1){
NSURL *phoneLinkURL = [NSURL URLWithString:phoneLinkString];
[[UIApplication sharedApplication] openURL:phoneLinkURL];
message = nil;
}
}
- (void)dealloc {
[phoneNumberString release];
[phoneNumberLabel release];
[super dealloc];
}
but it still crashed after clicking the button on the UIAlertview. The error is 0x3beb85b0: ldr r3, [r4, #8] EXC_BAD_ACCESS (code=1, address=0x7269634f) Any help would be appreciated. Thanks!
The problem might be that you set your alert to nil before your if statement. Try putting it after.
The crash is happening due to this code. [self release];.
When you call self release the view in which the alert is displayed will released and deallocated, not the alertView. That's the cause of crash.
You are already releasing the alertViews memory in the dialButtonPressed: method using [message autorelease];
So no need to release the alertView again in clickedButtonAtIndex. So change the method like:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if(alertView.tag == 0 && buttonIndex == 1)
{
NSURL *phoneLinkURL = [NSURL URLWithString:phoneLinkString];
[[UIApplication sharedApplication] openURL:phoneLinkURL];
}
message = nil;
}
Your crash is being caused by poor memory management. The primary issue is calling [self release]. It's a pretty rare case that this is appropriate.
Another issue is your attempt to check the message.tag right after setting message to nil. Calling the tag property on a nil object will always result in a value of 0.
Your dealloc method is all wrong. Don't call [self release]. Don't call [message release] since you autoreleased it when you showed it.
BTW - never use a tag of 0. This is the default. If you want to use the tag, always use a non-zero value so you can distinguish the value from the default.
I have several UIAlertViews that i want to display in a sequential order, and only move on to display the next UIAlertView once the previous one has been dismissed (by user clicking okay).
I know about the didDismissWithButtonIndex delegate and adding a tag, but this doesn't really help too much as there could be upto 3 UIAlertViews invoked and not necessarily in the same order everytime. see code:
if(condition 1){
alert1 = // UIAlertView[[.....
[alert1 show]
}
if(condition 2){
alert2 = // UIAlertView[[.....
[alert2 show]
}
if(condition 3){
alert3 = // UIAlertView[[.....
[alert3 show]
}
The above will just add 3 Alerts on top of each other (depending on how many conditions are met) which is not what I want. I want to be able to only show one at a time and then the next one (if there is one) after the user hits the ok button.
I had the idea of maybe adding the messages to a queue and then processing that queue removing the alert every time an alert is dismissed but i'm not sure how id go about doing that.
Any ideas would be much appreciated.
Thanks
You could easily do it in the UIAlertView delegate method, called once an alert view is dismissed. So, UIAlertViewDelegate defines the following delegate method:
– alertView:didDismissWithButtonIndex:
Implement that method, and make sure your class is the delegate of the UIAlertViews you create. This method is the perfect place to then show the next alert based on the one being dismissed by the user.
If your requirement is "Display up to three alerts, sequentially, but not always in the same order" i'd probably put the alerts in to an array, and then in the delegate method get the next alert out of the array to show. It doesn't have to be any more complex than that really; the key thing is that the delegate method implementation is the best place to show the next alert.
Pseudo Code Example:
Define an array; NSMutableArray * alerts_;
- (void)showAlertSequence {
if ( !alerts_ ) {
alerts_ = [[NSMutableArray alloc] init];
}
[alerts_ addObjects;<My alerts>];
[self showSequencedAlertFrom:nil];
}
- (BOOL)showSequencedAlertFrom:(UIAlertView *)sourceAlertView {
if ( !sourceAlertView ) {
[[alerts_ objectAtIndex:0] show];
}
else {
NSInteger index = [alerts_ indexOfObject:sourceAlertView];
if ( index < [alerts_ count] ) {
[[alerts_ objectAtIndex:index++] show];
}
}
return NO;
}
– alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)index {
// Show the next alert or clean up if we're at the end of the sequence.
if ( ![self showSequencedAlertFrom:alertView] ) {
[alerts_ removeAllObjects];
}
}
As an aside; three sequential alerts will really annoy you users ;)
One thing that I've done is used block based UIAlertViews by adding a category on AlertView.
Here is the .h file
#interface UIAlertView (WithBlocks)
- (id) initWithTitle:(NSString *)title message:(NSString *)message;
- (void) addButtonWithTitle:(NSString *)title andBlock:(void(^)())block;
#end
Here is the .m file
static NSString *BUTTON_BLOCK_KEY = #"alertview-button-blocks";
#interface UIAlertView()
- (void) runBlock: (void (^)())block;
#end
#implementation UIAlertView (WithBlocks)
/**
* Initialized an alert view with a title and message.
*/
- (id) initWithTitle:(NSString *)title message:(NSString *)message
{
self = [self initWithTitle:title message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
if (self) {
self.delegate = self;
NSMutableArray *buttonBlocks = [NSMutableArray array];
objc_setAssociatedObject(self, BUTTON_BLOCK_KEY, buttonBlocks, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return self;
}
/**
* Adds a button with a title and a block to be executed when that button is tapped.
*/
- (void) addButtonWithTitle:(NSString *)title andBlock:(void (^)())block
{
// Add the button
[self addButtonWithTitle:title];
NSMutableArray *buttonBlocks = objc_getAssociatedObject(self, BUTTON_BLOCK_KEY);
if (!block) {
block = ^{ /* empty block */ };
}
[buttonBlocks addObject:[[[block copy] retain] autorelease]];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
NSMutableArray *buttonBlocks = objc_getAssociatedObject(self, BUTTON_BLOCK_KEY);
void (^block)() = (void (^)()) [buttonBlocks objectAtIndex:buttonIndex];
// Due to a timing issue, the current window is still the UIAlertView for a very
// short amount of time after it has been dismissed which messes up anything
// trying to get the current window in the blocks being run.
// Ergo, the block is being delayed by a tiny bit. (Amount determined through limited testing)
[self performSelector:#selector(runBlock:) withObject:block afterDelay:0.25];
}
- (void) runBlock: (void (^)())block
{
block();
}
#end
Then you can call chain the alertviews together by the following code
void(^continueBlock)(void) = ^{
// Display more alertviews here
};
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"message"];
[alert addButtonWithTitle:#"Continue" andBlock:continueBlock];
[alert addButtonWithTitle:#"Dismiss" andBlock:^{
// Display more alertviews here
}
[alert show];
[alert release];
I was looking for a solution to this problem as well. Here's the way I ended up solving it for my own app:
static BOOL alertShowing = FALSE;
UIAlertView *alert0 = [[UIAlertView alloc] initWithTitle:#"AlertView 0" message:#"This is the first alert" delegate:self cancelButtonTitle:nil otherButtonTitles:#"Yes",#"No", nil];
[alert0 setTag:0];
alertShowing = TRUE;
[alert0 show];
while (alertShowing) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
}
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"AlertView 1" message:#"This is the second alert" delegate:self cancelButtonTitle:nil otherButtonTitles:#"Yes",#"No", nil];
[alert1 setTag:1];
alertShowing = TRUE;
[alert1 show];
while (alertShowing) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
}
// add some more alerts here for dramatic effect ...
Your button handler must set alertShowing = FALSE' in every exit path.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// Deal with handling responses for your different alerts here.
switch ([alertView tag]) {
case 0:
// handler first alert here
break;
case 1:
// handler second alert here
break;
default:
// etc.
break;
}
alertShowing = FALSE;
}
There may be better ways to sit and spin than creating a new run loop, and there's some duplicate code that probably could be genericized better. On the plus side, it's straightforward and doesn't require a bunch of queuing logic. I'm using a #define for this pattern to keep from having to hand-type it, and it has worked fine in my case.
Here's how I did it using a queue of alert's like you suggested.
#property (strong, nonatomic) NSMutableArray *alertQueue;
#property (nonatomic) BOOL showingAlert;
- (void)showAlert:(UIAlertView *)alert {
if (self.showingAlert) {
[self.alertQueue addObject:alert];
}
else {
self.showingAlert = YES;
[alert show];
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if ([self.alertQueue count]) {
UIAlertView *alert = [self.alertQueue objectAtIndex:0];
[self.alertQueue removeObjectAtIndex:0];
[alert show];
}
else self.showingAlert = NO;
}
Then whenever you want to display an alert, you just create the UIAlertView and pass it to the showAlert method and it will only show up after all earlier alerts have been dismissed.
I'm trying to read the EXIF data from an image, selected by a user. I'm using the ALAssetLibrary for this. So far I've managed to get the reference URL needed for the assetForURL:resultBlock:failureBlock: method, but when I try to do anything with the reference URL i get a EXC_BAD_ACCESS error.
An NSLog of the URL, right before using it, results in the (correct, as far as i know) string:
assets-library://asset/asset.JPG?id=1000000003&ext=JPG
I've been trying to figure this out, but I seem to be hitting a dead end each time. I must admit I'm new to Objective-C in general, so please feel free to criticize my code accordingly.
Code (far from the complete classes, but i think it should be sufficient):
//Class_X.m
-(void)readExifDataFromSelectedImage:(NSURL *)imageRefURL
{
void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *) = ^(ALAsset *asset)
{
NSLog(#"Test:Succes");
};
ALAssetsLibrary *myAssetLib;
NSLog(#"%#",imageRefURL);
[myAssetLib assetForURL:imageRefURL
resultBlock:ALAssetsLibraryAssetForURLResultBlock
failureBlock:^(NSError *error){NSLog(#"test:Fail");}];
}
//Class_Y.m
//This also conforms to the UIImagePickerControllerDelegate And the NavigationControllerDelegate protocols:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
self.referenceURL = [info valueForKey:#"UIImagePickerControllerReferenceURL"];
NSString *mediaType = [info
objectForKey:UIImagePickerControllerMediaType];
[self dismissModalViewControllerAnimated:YES];
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
UIImage *selectedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
imageView.image = selectedImage;
btnNoPicture.hidden = YES;
btnSelectPicture.hidden = YES;
btnTakePicture.hidden = YES;
imageView.hidden = NO;
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Use this image?"
message:#"Are you sure you want to use this image?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alert show];
[alert release];
}
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0)
{
//Do not use the selected image.
imageView.image = nil;
imageView.hidden = YES;
//Restart picking process
}
else
{
// I have an instance variable of type Class_X which i use
// throughout this class; let's call this variable "report".
// I also have the referenceURL stored as an instance variable.
[self.report readExifDataFromSelectedImage:self.referenceURL];
}
}
EXC_BAD_ACCESS is most often the result of an over-released object (dangling pointer). As the library operates asynchronously, your block is executed after the readExifDataFromSelectedImage: method has returned, so imageRefURL is probably already deallocated at this point. Try to retain the URL before requesting the asset and release it in the success and failure blocks.
I need make UIAlertView blocking. Because i have function and i need to return UIAlertView choice. But problem is that after UIAlertView is shown my function code is executing further so i can't catch UIAlertView choice (i can do it in delegate methods, but i need to return function result).
I tried to make UIAlertVIew blocking with NSCondition. But the code don't works.
condition = [NSCondition new];
result = 0 ;
[condition lock];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Fingerprint" message:#"test" delegate:window_self cancelButtonTitle:#"No" otherButtonTitles:#"Yes",nil];
[alert setDelegate:self];
[alert show];
while (result == 0) [condition wait];
[condition unlock] ;
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[condition lock] ;
if (buttonIndex == 0)
{
result = 2;
}
else if (buttonIndex == 1)
{
result = 3 ;
}
[condition signal] ;
[condition unlock] ;
}
Maybe how to fix this code or any other suggestions ? Thanks
There's no way to achieve what you want. Only through the delegate. You should redesign your function or refuse using UIAlertView
This doesn't make it blocking, but I have written a subclass to add block style syntax which makes it much easier to handle the buttonClickedAtIndex method without having to do a delegate and a whole bunch of if statements if you have multiple UIAlertViews in one class.
#import <UIKit/UIKit.h>
#interface UIAlertViewBlock : UIAlertView<UIAlertViewDelegate>
- (id) initWithTitle:(NSString *)title message:(NSString *)message block: (void (^)(NSInteger buttonIndex))block
cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_AVAILABLE(10_6, 4_0);
#end
#import "UIAlertViewBlock.h"
#interface UIAlertViewBlock()
{
void (^_block)(NSInteger);
}
#end
#implementation UIAlertViewBlock
- (id) initWithTitle:(NSString *)title message:(NSString *)message block: (void (^)(NSInteger buttonIndex))block
cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_AVAILABLE(10_6, 4_0)
{
if (self = [super initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:otherButtonTitles, nil])
{
_block = block;
}
return self;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
_block(buttonIndex);
}
#end
Then to call it here is some example code. The other cool part is that because a block closes around the local variables, I can have access to all the state that existed at the time I show the UIAlertView. Using the traditional delegate approach, you would have to store all that temporary state into class level variables to have access to it in the call to buttonClickedAtIndex in the delegate. This is so much cleaner.
{
NSString *value = #"some random value";
UIAlertViewBlock *b = [[UIAlertViewBlock alloc] initWithTitle:#"Title" message:#"Message" block:^(NSInteger buttonIndex)
{
if (buttonIndex == 0)
NSLog(#"%#", [value stringByAppendingString: #" Cancel pressed"]);
else if (buttonIndex == 1)
NSLog(#"Other pressed");
else
NSLog(#"Something else pressed");
}
cancelButtonTitle:#"Cancel" otherButtonTitles:#"Other", nil];
[b show];
}
I was just facing the same problem. Although no solution, there are at least 2 workarounds that I thought of.
Loop "solution"
Right after you call the UIAlert you start a loop that looks for the change in a variable that is global to your object (not to the whole project, mind you) that variable is the one you set in the UIAlert delegate that takes the answers. So basically you wait for "is A == 1, if not DoEvents" and loop on it.
Then on the delegate you make A=1 when you have the answer
and before someone says that there is no DoEvents in Cocoa:
void MyTestClass::DoEvents()
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantPast]
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event) {
[NSApp sendEvent:event];
[NSApp updateWindows];
}
[pool release];
}
Delegate Solution
Instead of having the code that deals with Answer A, B or C in the function that calls the Alert, have the code in the delegate itself.
Hope it helps.
I used the second one in my project and it worked.
I just found this question by accident and even by entering Apple hell by posting this, I hereby proclaim this as a proof of concept:
#interface EvilShitClass () <UIAlertViewDelegate>
#end
#implementation EvilShitClass {
BOOL _isCanceled, _wasYes;
}
Here is the static method for a yes/no query:
+ (BOOL)yesNoQueryWithTitle:(NSString*)title text:(NSString*)text {
EvilShitClass *shit = [EvilShitClass new];
UIAlertView *alertView = [UIAlertView new];
alertView.delegate = shit;
alertView.title = title;
alertView.message = text;
[alertView addButtonWithTitle:#"Yes"];
[alertView addButtonWithTitle:#"No"];
NSRunLoop *run_loop = [NSRunLoop currentRunLoop];
[alertView show];
while( !shit->_isCanceled ) {
BOOL tmp = [run_loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
}
return shit->_wasYes;
}
and finally the delegate method for handling the button click and
stop the runloop-processing:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
_wasYes = (buttonIndex == 0);
_isCanceled = YES;
}
This works but remember: you shouldn't do it this way :-D
Pretty please don't argue about style and stuff, it's just a 5 minutes quick hack to proof it can be done! This should work without ARC (new -> autorelease)
but if I'm wrong you know how to handle it ;)
Disclaimer: I'm not responsible for any possible damage the use of this snippet could do to your application or devices. Thank you.
Use the UIAlertView with blocks from Joseph and add a semaphore to it.
Declare a global semaphore
dispatch_semaphore_t generateNotificationsSemaphore;
And signal the semaphore in the block handler
[alert showWithHandler:^(UIAlertView *alertView, NSInteger buttonIndex) {
if (buttonIndex == [alertView cancelButtonIndex]) {
} else {
}
dispatch_semaphore_signal(generateNotificationsSemaphore);
}];
After calling the showWithHandler add a waiting loop using the semaphore
while (dispatch_semaphore_wait(generateNotificationsSemaphore, DISPATCH_TIME_NOW )) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:20]];
}
Your actual timeout value may be different depending on your needs.