Objective C blocks in an array passed to a subclass of UIAlertView

(A working solution based on the answers is provided at the end of this publication.)

I thought this would be a neat way to handle callbacks that need to be addressed to a specific type of alert, so I don't have a single delegate method that filters out all clicks of the alert buttons. 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 configured 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 to one of the blocks, for example

 - (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 an EXC_BAD_ACCESS error.

Am I doing something fundamentally wrong or is there a small mistake in my logic?

UPDATE

Handled the variational problem using the Firoze sentence below. (Follow the examples given in 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 
+4
source share
2 answers

So, I see a couple of problems with this.

First, you need to copy these blocks when you put them in an array. These blocks are created on the stack. If you want to pass them into your warning, and you expect the type of warning to be held on them for later use, you must first copy them into a heap.

So something like this should work:

 NSArray *actionBlocks = [NSArray arrayWithObjects: [[^{NSLog(@"Cancel Button Selected");} copy] autorelease], [[^{ [mapController.theMap removeAnnotations:mapController.theMap.annotations]; [dataInterface deleteDB]; [[NSNotificationCenter defaultCenter] postNotificationName:@"changeToFavorites" object:nil]; NSLog(@"Delete Button Selected"); } copy] autorelease] , nil]; 

Notice the copy of [^ someBlock] around each block literal. This should solve one problem.

Another problem that I do not know the answer to is that it is a variational method (accepts a variable number of arguments). I don’t know how to expand and call another variational method (UIAlertView initializer) in the variational method, if you do not have a variant of the second method that accepts va_list. This is the same problem that we have in C, inherited in Objective-C, as I understand it.

I think you have not encountered this yet, because you have not tried enough buttons for this.

EDIT

Thinking about this further, I think you can get around the second problem, iterating through varargs, and then calling [self addButtonWithTitle: arg] for each of them.

+2
source

You may find the Lambda Alert useful:

 LambdaAlert *alert = [[LambdaAlert alloc] initWithTitle:@"Test Alert" message:@"See if the thing works."]; [alert addButtonWithTitle:@"Foo" block:^{ NSLog(@"Foo"); }]; [alert addButtonWithTitle:@"Bar" block:^{ NSLog(@"Bar"); }]; [alert addButtonWithTitle:@"Cancel" block:NULL]; [alert show]; 

and

 LambdaSheet *sheet = [[LambdaSheet alloc] initWithTitle:@"Action Sheet"]; [sheet addButtonWithTitle:@"Miles" block:^{ NSLog(@"Trumpet"); }]; [sheet addButtonWithTitle:@"Trane" block:^{ NSLog(@"Saxophone"); }]; [sheet addDestructiveButtonWithTitle:@"Monk" block:^{ NSLog(@"Piano"); }]; [sheet addCancelButtonWithTitle:@"Back to the Head"]; [sheet showInView:window]; 

A static library that is easily included in your project using the Xcode workspace.

+1
source

Source: https://habr.com/ru/post/1383372/


All Articles