EXC_BAD_ACCESS error using ALAssetsLibrary assetForURL:resultBlock:failureBlock: - objective-c

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.

Related

hide UIViewController in objective-c

I have a home page with table and one button, with pressing my button I will load the qrCode reader view, I want to hide my qrCode view after scan the barcode,
would you please give me some hint what is the best way to do this? NSNOtification or ...
and how?
Thanks in advance!
Here is my qr view code that scans the code:
- (void) readerView:(ZBarReaderView *)readerView didReadSymbols: (ZBarSymbolSet *)symbols
fromImage:(UIImage *)image
{
ZBarSymbol * s = nil;
for (s in symbols)
{
image_view.image = image;
// here I will go to httpReq class for addStamp method
[[HttpReq sharedHttp] setCode:s.data];
[[HttpReq sharedHttp] addStamp];
}
}
Here is my addStamp method that I have to hide the view in this method:
-(void) addStamp
{
// My code was here for connect to api
NSError *jsonError;
NSDictionary* dic = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&jsonError];
if(jsonError != nil)
{
return;
}
// I want to do it here when I have a success or not
BOOL success = [[dic objectForKey:#"success"] boolValue];
if(success){
[self addStampInDB:[[dic objectForKey:#"cardId"] intValue]];
}else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:[dic
objectForKey:#"error"]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert performSelectorOnMainThread:#selector(show) withObject:nil
waitUntilDone:NO];
}
}];
}
Hiding a view in Objective-C is quite simple:
[theView setHidden:YES];
When the addStamp method is called you could invoke a method that is defined in the first class using performSelector or you could follow protocol and delegate mechanism to keep it neat.

iOS EXC_BAD_ACCESS error in class

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];

iOS: Simple NSMutableArray Call

I need to call removeObject in one of my methods, but I can't figure out how to do this correctly. I'm very new to Objective-C, and am still learning the basics. I have an app that behaves somewhat like a photo gallery, and displays UIImageViews. I'm implementing the option to have the user delete photos from their gallery. To accomplish this, I decided to place an invisible button over each picture. When the user hits an "Edit" button, the hidden delete button over each picture becomes active (I'm using the same IBOutlet over each of the hidden buttons, for simplicity). When the user taps the button over the picture, an alert view appears asking if they really want to delete it. If they click yes, deleteAlertView comes into play:
- (void)deleteAlertView:(UIAlertView *)deleteButtonPressed
didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [deleteButtonPressed cancelButtonIndex]) {
[array removeObject:#"%#", deleteButtonPressed];
}
The issue here is [array removeObject:#"%#", deleteButtonPressed];, I did the %# so that this will automatically determine which object in the array was tapped, rather than manually putting in a new method and button for each UIImageView (I may have to end up doing that). I'm getting errors regarding "array" and "deleteButtonPressed" (use of undeclared identifier), I can't for the life of me figure out what to put instead. I'm still learning the basics and how inheritance in this language works. Any help or advice would be great! I should probably post the whole view controller file to show the related inheritance:
- (IBAction)grabImage {
self.imgPicker = [[UIImagePickerController alloc] init];
self.imgPicker.delegate = self;
self.imgPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
_popover = [[UIPopoverController alloc] initWithContentViewController:imgPicker];
[_popover presentPopoverFromRect:self.imageView.bounds inView:self.imageView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
else {
[self presentModalViewController:imgPicker animated:YES];
}
[self.imgPicker resignFirstResponder];
}
// Sets the image in the UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo {
if (imageView.image == nil) {
imageView.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView2.image == nil) {
imageView2.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
if (imageView3.image == nil) {
imageView3.image = img;
[picker dismissModalViewControllerAnimated:YES];
[self.popover dismissPopoverAnimated:YES];
return;
}
}
- (void)viewWillAppear:(BOOL)animated
{
self.user = [NSUserDefaults standardUserDefaults];
NSMutableArray* array = [[self.user objectForKey:#"images"]mutableCopy];
while(array == nil)
{
[self.user setObject:[NSMutableArray arrayWithObject:#""] forKey:#"images"];
array = [[self.user objectForKey:#"images"]mutableCopy];
NSLog(#"%#",#"attempting to create an array to store the images in");
}
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
NSLog(#"Image on didenterbackground: %#", imageView);
NSMutableArray* array = [NSMutableArray arrayWithObject:[NSData dataWithData:UIImagePNGRepresentation(imageView.image)]];
[array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView2.image)]];
[array addObject:[NSData dataWithData:UIImagePNGRepresentation(imageView3.image)]];
[self.user setObject:array forKey:#"images"];
[user synchronize];
}
- (void)viewDidLoad
{
self.user = [NSUserDefaults standardUserDefaults];
NSLog(#"It is %#", self.user);
NSMutableArray* array = [[self.user objectForKey:#"images"]mutableCopy];
imageView.image = [[UIImage alloc] initWithData:[array objectAtIndex:0]];
imageView2.image = [[UIImage alloc] initWithData:[array objectAtIndex:1]];
imageView3.image = [[UIImage alloc] initWithData:[array objectAtIndex:2]];
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:app];
backToGalleryButton.hidden = YES;
tapToDeleteLabel.hidden = YES;
deleteButton1.hidden = YES;
[super viewDidLoad];
}
- (IBAction)deleteButtonPressed:(id)sender {
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
[deleteAlertView show];
}
- (void)deleteAlertView:(UIAlertView *)deleteButtonPressed
didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex != [deleteButtonPressed cancelButtonIndex]) {
[array removeObject:#"%#", deleteButtonPressed];
}
}
There is one thing wring here, but first a relatively minor point:
- (void)deleteAlertView:(UIAlertView *)deleteButtonPressed didDismissWithButtonIndex:(NSInteger)buttonIndex
the phrase "deleteButtonPressed" implies an even, as it ends in a verb. It Is actually referring to an object, specifically a parameter of the type UIAlertView. you should call it something more like AlertView.
Secondly this line is quite wrong:
[array removeObject:#"%#", deleteButtonPressed];
You are trying to remove a string. If that method accepted an argument list (where you pass multiple objects separated by a comma), you would be removing literally "deleteButtonPressed". You want to remove the object that is being pointed to by the deleteButtonPressed variable. So all you have to do is:
[array removeObject:deleteButtonPressed];
The issue here is [array removeObject:#"%#", deleteButtonPressed];
Yes, that is one of the issues (even ignoring the invalid syntax). The array does not contain your UIAlertView, it contains whatever objects [user objectForKey:#"images"] contains. Which seem like they should be NSData instances and which in any case are definitely not your UIAlertView instance(s).
So in other words, you can't pass the UIAlertView to the array in order to have the array magically work out what item the UIAlertView is supposed to correspond to. Instead what you should do is tag the UIAlertView with the index it corresponds to when you create it. You can do this like:
UIAlertView *deleteAlertView = [[UIAlertView alloc] initWithTitle:#"Delete"
message:#"Are you sure you want to delete this photo?"
delegate:self
cancelButtonTitle:#"Yes"
otherButtonTitles:#"No", nil];
int imageIndex = <figure out the index of the associated array element based upon 'sender'>;
deleteAlertView.tag = imageIndex;
...and then when the button is pressed, you do:
[array removeObjectAtIndex:deleteButtonPressed.tag];
And to fix up that "undeclared identifier" issue, you should declare array in your header and not in viewDidLoad. You want it to be a private instance variable, not a local variable.
Also note that deleting an element from [[user objectForKey:#"images"] mutableCopy] will not automatically cause the corresponding element to be deleted from [user objectForKey:#"images"]. You need to write the modified array back to [NSUserDefaults standardUserDefaults] if you want the modification to actually persist.
You are getting the error "Use of undeclared identifier array" because you declare the array in different methods, but not in your deleteAlertView method. I suggest reading up on variable scope.
Fixing that, however, will not get your code to work because you have some fundamental design flaws that need to be worked out.
You mention that you are a beginner, so I would suggest reading through and completing several beginner tutorials before attempting this app. I know it is fun to dive right into a project, but you will very likely get frustrated and also develop bad habits with respect to app design/engineering. In particular, I would try to get a firmer understand of variable scope and MVC design patterns.

Saving video after tapping use in UIImagePickerController

I am have an app that calls UIImagePickerController, and you take video using the controller. After you take the video, I want the video to be saved into the camera roll after clicking use in the UIImagePickerController.
Can you help me?
Oh, and UIImagePickerController comes with the option to switch to video, default photo. I want this to be only video, not photo.
My current code is :
picker.sourceType=UIImagePickerControllerCameraCaptureModeVideo;
picker.mediaTypes=[UIImagePickerController availableMediaTypesForSourceType:picker.sourceType];
Once you have implemented the following delegate method, pressing the 'Use' button will save your recorded video to the camera roll.
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSURL *recordedVideoURL= [info objectForKey:UIImagePickerControllerMediaURL];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:recordedVideoURL]) {
[library writeVideoAtPathToSavedPhotosAlbum:recordedVideoURL
completionBlock:^(NSURL *assetURL, NSError *error){}
];
}
[library release];
}
Use the following to set the capture mode to video :
imagePickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
Here is the complete code that I use to launch the image picker for video recording :
UIImagePickerController * imagePickerController = [[UIImagePickerController alloc] init];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
imagePickerController.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeMovie];
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePickerController.showsCameraControls = YES;
imagePickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
imagePickerController.delegate = self;
imagePickerController.cameraDevice = UIImagePickerControllerCameraDeviceFront;
[self presentModalViewController:imagePickerController animated:YES];
}
I was trying to save the video using the absoluteURL of the URL but it didn't work
NSURL *mediaURL = [info objectForKey: UIImagePickerControllerMediaURL];
UISaveVideoAtPathToSavedPhotosAlbum(mediaURL.absoluteString, self, #selector(video:didFinishSavingWithError:contextInfo:), nil);
It finally worked using the path property:
UISaveVideoAtPathToSavedPhotosAlbum([mediaURL path], self, #selector(video:didFinishSavingWithError:contextInfo:), nil);
Hope it helps anyone.
From the UIImagePickerControllerDelegate class reference:
To save a still image to the user’s Camera Roll album, call the
UIImageWriteToSavedPhotosAlbum function from within the body of the
imagePickerController:didFinishPickingMediaWithInfo: method. To save a
movie to the user’s Camera Roll album, instead call the
UISaveVideoAtPathToSavedPhotosAlbum function. These functions,
described in UIKit Function Reference, save the image or movie only;
they do not save metadata.
Your UIImagePickerControllerDelegate may look something like this...
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
NSString *mediaType = [info objectForKey:#"mediaType"];
if (![mediaType isEqualToString:kUTTypeMovie])
return;
NSURL *mediaURL = [info objectForKey:#"mediaURL"];
UISaveVideoAtPathToSavedPhotosAlbum(mediaURL.absoluteString, self, #selector(video:didFinishSavingWithError:contextInfo:), NULL);
}
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo: (void *)contextInfo
{
if (!videoPath && error)
{
NSLog(#"Error saving video to saved photos roll: %#, %#", error, [error userInfo]);
// Handle error;
return;
}
// Video was saved properly. UI may need to be updated here.
}
Notice that I've implemented the completion selector for UISaveVideoToSavedPhotosAlbum(). If it were to fail for any reason, the error would be reported here and you can then handle it appropriately.

Displaying UIAlertViews Sequentially

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.