(A working solution, based on the responses, is provided at the end of this post.)
I thought this would be a tidy way to handle the callbacks that a particular alert view needs to address, so I don’t have a single delegate method filtering all of the alert button presses. Here is the code:
#import "LSAlertView.h"
@implementation LSAlertView
- (id) initWithTitle:(NSString *)title
message:(NSString *)message
actionBlocks:(NSArray*)_actionBlocks
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ...
{
self = [super initWithTitle:title
message:message
delegate:self
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles,nil];
if (self) {
self.cancelButtonIndex = 0;
actionBlocks = [_actionBlocks retain];
[self show];
}
return self;
}
- (void) dealloc {
[actionBlocks release];
[super dealloc];
}
- (void) alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
void (^action)(void) = [actionBlocks objectAtIndex:buttonIndex];
action();
}
@end
This works fine for two buttons set up like this:
- (void) restartSearches {
NSArray *actionBlocks = [NSArray arrayWithObjects:
^{NSLog(@"Cancel Button Selected");},
^{NSLog(@"Delete Button Selected");},
nil];
alertDeletingSearches = [[LSAlertView alloc]
initWithTitle:@"You Are About To Delete Your Current Searches"
message:@"Select Delete to Continue"
actionBlocks:actionBlocks
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Delete", nil];
[alertDeletingSearches release];
}
But as soon as I add some useful calls in one of the blocks, like this
- (void) restartSearches {
NSArray *actionBlocks = [NSArray arrayWithObjects:
^{NSLog(@"Cancel Button Selected");},
^{
[mapController.theMap removeAnnotations:mapController.theMap.annotations];
[dataInterface deleteDB];
[[NSNotificationCenter defaultCenter]
postNotificationName:@"changeToFavorites"
object:nil];
NSLog(@"Delete Button Selected");
},
nil];
alertDeletingSearches = [[LSAlertView alloc]
initWithTitle:@"You Are About To Delete Your Current Searches"
message:@"Select Delete to Continue" actionBlocks:actionBlocks
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Delete", nil];
[alertDeletingSearches release];
}
it freezes, and I get a EXC_BAD_ACCESS error.
Am I doing something fundamentally wrong, or is there a minor error in my logic?
UPDATE
Handled the variadic problem problem using Firoze’s suggestion below. (Follows the examples given at Numbergrinder)
- (id) initWithTitle:(NSString *)title message:(NSString *)message actionBlocks:(NSArray*)_actionBlocks cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {
self = [super initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:otherButtonTitles, nil];
if (self) {
va_list args;
va_start(args, otherButtonTitles);
NSString* buttonTitle;
while ((buttonTitle = va_arg(args, NSString *))) {
[super addButtonWithTitle:buttonTitle];
}
self.cancelButtonIndex = 0;
actionBlocks = [_actionBlocks retain];
[self show];
}
return self;
}
Here is the header file:
@interface LSAlertView : UIAlertView <UIAlertViewDelegate> {
NSArray *actionBlocks;
}
- (id) initWithTitle:(NSString *)title message:(NSString *)message actionBlocks:(NSArray*)_actionBlocks cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...;
@end
So I see a couple of issues with this.
One is that you need to copy those blocks as you put them in the array. Those blocks are created on the stack. If you want to pass them to your alert view and you expect the alert view to hold onto them for later use, you need to copy them to the heap first.
So something like this should work:
Note the [^someBlock copy] around each block literal there. That should solve one issue.
The other issue, to which I don’t know the answer, is that this is a variadic method (takes a variable number of arguments). I don’t know of a way in a variadic method to turn around and call another variadic method (the UIAlertView initializer), unless you have a variation of the second method that takes a va_list. This is the same issue we have in C, inherited in Objective C as far as I understand it.
I think you haven’t run into that yet because you haven’t tried enough buttons for that.
EDIT
Thinking about this further, I guess you could get around the second issue by iterating through the varargs and then calling [self addButtonWithTitle:arg] for each of them.