UIAlertView with a user supplied context and [self autorelease] - objective-c

I have looked over some ideas for how to supply a context to a UIAlertView. The common answers are save it in a dictionary or subclass UIAlertView. I don't like the idea of saving the context in a dictionary, it's the wrong place for the data. Subclassing UIAlertView is not supported by Apple, so by my standard, is not a good solution.
I came up with an idea, but I'm not sure what to make of it. Create an instance of a context object that is the delegate of UIAlertView. The alert view context, in turn, has it's own delegate which is the view controller.
The trouble is releasing memory. I set alertView.delegate to nil and call [self autorelease] to free the context object in -alertView:didDismissWithButtonIndex:.
THE QUESTION IS: What problems am I causing myself? I have a suspicion that I'm setting myself up for a subtle memory error.
Here is the simple version which only supports -alertView:clickedButtonAtIndex:
Use
- (void)askUserIfTheyWantToSeeRemoteNotification:(NSDictionary *)userInfo
{
[[[[UIAlertView alloc] initWithTitle:[userInfo valueForKey:#"action"]
message:[userInfo valueForKeyPath:#"aps.alert"]
delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]
cancelButtonTitle:#"Dismiss"
otherButtonTitles:#"View", nil] autorelease] show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex withContext:(id)context
{
if (buttonIndex != alertView.cancelButtonIndex)
[self presentViewForRemoteNotification:context];
}
Interface
#protocol WantAlertViewContextDelegate <NSObject>
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex withContext:(id)context;
#end
#interface WantAlertViewContext : NSObject <UIAlertViewDelegate>
- (id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context;
#property (assign, nonatomic) id<WantAlertViewContextDelegate> delegate;
#property (retain, nonatomic) id context;
#end
Implementation
#implementation WantAlertViewContext
- (id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context
{
self = [super init];
if (self) {
_delegate = delegate;
_context = [context retain];
}
return self;
}
- (void)dealloc
{
[_context release];
[super dealloc];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
[self.delegate alertView:alertView clickedButtonAtIndex:buttonIndex withContext:self.context];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
alertView.delegate = nil;
[self autorelease];
}
#synthesize delegate = _delegate;
#synthesize context = _context;
#end

You can use the concept of associated objects. Using the functions objc_setAssociatedObject() and objc_getAssociatedObject(). You can use these properties to essentially add a new property, in your case to hold an NSDictionary, to an object through a category.
Here is an example of a UIAlertView category. These files should be compiled without ARC, -fno-objc-arc flag set if the project is using ARC.
UIAlertView+WithContext.h:
#import <UIKit/UIKit.h>
#interface UIAlertView (Context)
#property (nonatomic, copy) NSDictionary *userInfo;
#end
UIAlertView+WithContext.m:
#import "UIAlertView+WithContext.h"
// This enum is actually declared elseware
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
#implementation UIAlertView (Context)
static char ContextPrivateKey;
-(void)setUserInfo:(NSDictionary *)userInfo{
objc_setAssociatedObject(self, &ContextPrivateKey, userInfo, 3);
}
-(NSDictionary *)userInfo{
return objc_getAssociatedObject(self, &ContextPrivateKey);
}
#end
This category is easily used.
SomeViewController.m: a UIAlertViewDelegate using ARC or not.
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
alert.userInfo = [NSDictionary dictionaryWithObject:#"Hello" forKey:#"Greeting"];// autorelease if MRC
[alert show]; // release if MRC
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
NSLog(#"userInfo:%#",alertView.userInfo);
}
When you press the alertview's OK button you will see:
userInfo:{
Greeting = Hello;
}
A couple of notes:
1) Make sure the association type matches the property declaration so things behave as expected.
2) You probably shouldn't use userInfo for the property/association since Apple may well decide to add a userInfo property to UIAlertView in the future.
Edit To address your concerns about your [self autorelease];
It is imperative that you balance your implicit alloc retain from this line: delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]. You achieve this balance by calling [self autorelease]; in the final UIAlertView delegate method.
Granted, this does feel wrong. Mostly because there is no way when looking at this that it doesn't at first blush look like memory mis-management. But there is one simple way to avoid this "controlled leak" API you are creating; Have the instance of WantAlertViewContext explicitly retain itself. For example:
-(id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context{
self = [super init];
if (self) {
_delegate = delegate;
_context = [context retain];
}
return [self retain]; // Explicitly retain self
}
-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
alertView.delegate = nil;
[self autorelease]; // Or just [self release]; doesn't make much difference at this point
}
Now your class has some internal harmony. I say some because this is still not perfect. For example, if an instance is never an alert-view delegate it will never be released. It is still just a "semi-controlled" memory leak.
Anyway, now your instantiation call can look more logical:
delegate:[[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo] autorelease];
I think that this particular design pattern is fraught with danger. If you do end up using it keep a close eye on it.

I've come up with a simpler solution that may fit in some circumstances. Because you get the NSAlertView context when the delegate gets called, I use the actual address of the object to make a tag (NSString*) which I then use to store custom values in a global or object specific NSDictionary. Here is an example:
+(NSString*)GetTag:(id)ObjectIn
{
return [NSString stringWithFormat:#"Tag-%i",(int)ObjectIn];
}
In the Delegate:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* MyID = [CommandManager GetTag:alertView];
[CurrentActiveAlerts removeObjectForKey:MyID];
}
Calling:
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:title_text
message:#""
delegate:self
cancelButtonTitle:nil
otherButtonTitles:button_text ,nil];
CurrentActiveAlerts[[CommandManager GetTag:myAlert]] = CommandToRun; // Querky way to link NSDict to UIAlert, but the best I could think of
[myAlert show];
[myAlert release];
The keys will end up looking like "Tag-226811776". Hope this helps.

Related

How to implement a class method with a block completion handler

I'm trying to implement a fire-and-forget class method similar to
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
in the NSURLConnection, but I'm slightly confused about the memory management (I'm NOT using ARC at the moment).
My current code goes like this:
#interface StuffInfoDownloader() <UIAlertViewDelegate>
typedef void (^StuffInfoDownloaderCompletionBlock)(NSArray *stuffs);
- (id)initStuffsWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler;
#property (retain, nonatomic) StuffInfoDownloaderCompletionBlock completionHandler;
#property (retain, nonatomic) NSSet *identifiers;
#end
#implementation StuffInfoDownloader
#synthesize completionHandler = _completionHandler;
#synthesize identifiers = _identifiers;
+ (void)loadAsynchronouslyWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler
{
StuffInfoDownloader *downloader = [[StuffInfoDownloader alloc] initStuffsWithIdentifiers:identifiers completionHandler:handler];
[downloader downloadStuffs];
[downloader release]; // will retain itself
}
- (id)initStuffsWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler
{
if (!(self = [super init])) {
return nil;
}
[self retain];
_completionHandler = handler;
_identifiers = identifiers;
return self;
}
- (void)downloadStuffs
{
__block StuffInfoDownloader *me = self; // avoid reference cycle between self and the block
[StuffsConnection loadAsynchronouslyWithIdentifiers:self.identifiers completionHandler:
^(NSArray *stuffs, NSError *error) {
if(error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Failed."
message:#"TODO do localised string"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil];
[alert show];
[alert release];
} else {
me.completionHandler(stuffs);
[self release];
}
}];
}
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
#pragma unused(alertView, buttonIndex)
// try again
[self downloadStuffs];
}
- (void)dealloc
{
[_completionHandler release];
[_identifiers release];
[super dealloc];
}
Basically, I'm passing ownership of the object to itself, and releasing it in the handler. Any problems with that?
There are so many things wrong with this code. Besides the block property needing to be copy. You shouldn't do the [self retain]; and [self release]; (p.s. you missed a [self release] in the error case). That completely goes against the memory management rules. They are completely unnecessary if you do things right. Memory management in Cocoa is completely local -- a function or method needs only care what it does, not what any other code does. init has no reason to do [self retain], and does not have to "worry" about what any other code does. Period.
Then the _completionHandler = handler; _identifiers = identifiers; are wrong. The block needs to be copied if you are storing it in an instance variable; and the set needs to be retained or copied. You need to do either _completionHandler = [handler copy]; _identifiers = [identifiers retain]; or use the setter self.completionHandler = handler; self.identifiers = identifiers;.
Then, there is no issue of "retain cycle". A retain cycle requires a cycle -- A retains B, and B retains A. The block retains self, but does self retain the block? I don't see that anywhere. You are simply calling a class method of another class on this block. So you shouldn't do the weak reference. The weak reference is not correct anyway, since there is no guarantee that the current object will be valid by the time the block executes.
It seems that you (incorrectly) did the whole [self retain] thing, all in order to deal with the fact that you (also incorrectly) did not allow the block to retain self, as it should. Just get rid of this weak reference stuff, and get rid of the [self retain] stuff, and then it will not only follow the memory management rules, be more robust, but also look cleaner, simpler, and more understandable.
#property (nonatomic, copy) StuffInfoDownloaderCompletionBlock
completionHandler;
then in init:
self.completionHandler = handler;
You should never retain block if u haven't copied it before, that doesn't make sense .
By the way
if ((self = [super init])) {
/* initialization stuff*/
}
return self;
Seems that your code has lot of retainCycle flaws design

Delegating UIAlertView to another class / file? Not working?

so I'm new to iOS development and I'm trying to delegate the button click event to another class. Whenever I click a button on the alert, the app crashes and I get an error saying Thread_1 EXC_BAD_ACCESS.
This is my code.
// theDelegateTester.h
#import <UIKit/UIKit.h>
#interface theDelegateTester : UIResponder <UIAlertViewDelegate>
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
#end
Implementation..
// theDelegateTester.m
#import "theDelegateTester.h"
#implementation theDelegateTester
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(#"Delegated");
}
#end
And here's the implementation for my view file..
#import "appleTutorialViewController.h"
#import "theDelegateTester.h"
#interface appleTutorialViewController ()
- (IBAction)tapReceived:(id)sender;
#end
#implementation appleTutorialViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)tapReceived:(id)sender {
theDelegateTester *newTester = [[theDelegateTester alloc] init];
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Alert!" message:#"This is a delegated alert" delegate:newTester cancelButtonTitle:#"Close" otherButtonTitles:#"Cool!", nil];
[myAlert show];
}
#end
First of all, you should always start your class names with a capital letter, so you can differentiate between classes and instances or methods easily.
And you probably leak the delegate class. You should declare a strong/retained property TheDelegateTester *myDelegate in your view controller. Then in tapReceived: something like this:
- (IBAction)tapReceived:(id)sender {
if (!self.myDelegate) {
TheDelegateTester *del = [[TheDelegateTester alloc] init];
self.myDelegate = del;
[del release];
}
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Alert!" message:#"This is a delegated alert" delegate:newTester cancelButtonTitle:#"Close" otherButtonTitles:#"Cool!", nil];
[myAlert show];
[myAlert release];
}

Best Technique for Replacing Delegate Methods with Blocks

I'm looking to create a category to replace delegate methods with callbacks blocks for a lot of the simple iOS APIs. Similar to the sendAsyc block on NSURLConnection. There are 2 techniques that are leak free and seem to work fine. What are the pros/cons about each? Is there a better way?
Option 1. Use a category to implement the delegate's callback method on NSObject with the external callback block scoped.
// Add category on NSObject to respond to the delegate
#interface NSObject(BlocksDelegate)
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex;
#end
#implementation NSObject(BlocksDelegate)
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// Self is scoped to the block that was copied
void(^callback)(NSInteger) = (id)self;
// Call the callback passed if
callback(buttonIndex);
[self release];
}
#end
// Alert View Category
#implementation UIAlertView (BlocksDelegate)
+ (id) alertWithTitle:(NSString*)title
message:(NSString*)message
clickedBlock:(void(^)(NSInteger))buttonIndexClickedBlock
cancelButtonTitle:(NSString*)cancelButtonTitle
otherButtonTitles:(NSString*)otherButtonTitles
{
// Copy block passed in to the Heap and will stay alive with the UIAlertView
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:[buttonIndexClickedBlock copy]
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
// Display the alert
[alert show];
// Autorelease the alert
return [alert autorelease];
}
#end
This adds a lot of methods on the NSObject and seems like it could cause issues with any other class trying to use the standard delegate method. But it keeps the block alive with the object and returns the callback without any leaks that I've found.
Option 2. Create an light-weight class to contain the block, dynamicly associate it with the class so it will stay in the heap and remove it when the callback is complete.
// Generic Block Delegate
#interface __DelegateBlock:NSObject
typedef void (^HeapBlock)(NSInteger);
#property (nonatomic, copy) HeapBlock callbackBlock;
#end
#implementation __DelegateBlock
#synthesize callbackBlock;
- (id) initWithBlock:(void(^)(NSInteger))callback
{
// Init and copy Callback Block to the heap (#see accessor)
if (self = [super init])
[self setCallbackBlock:callback];
return [self autorelease];
}
- (void) dealloc
{
// Release the block
[callbackBlock release], callbackBlock = nil;
[super dealloc];
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// Return the result to the callback
callbackBlock(buttonIndex);
// Detach the block delegate, will decrement retain count
SEL key = #selector(alertWithTitle:message:clickedBlock:cancelButtonTitle:otherButtonTitles:);
objc_setAssociatedObject(alertView, key, nil, OBJC_ASSOCIATION_RETAIN);
key = nil;
// Release the Alert
[alertView release];
}
#end
#implementation UIAlertView (BlocksDelegate)
+ (id) alertWithTitle:(NSString*)title
message:(NSString*)message
clickedBlock:(void(^)(NSInteger))buttonIndexClickedBlock
cancelButtonTitle:(NSString*)cancelButtonTitle
otherButtonTitles:(NSString*)otherButtonTitles
{
// Create class to hold delegatee and copy block to heap
DelegateBlock *delegatee = [[__DelegateBlock alloc] initWithBlock:buttonIndexClickedBlock];
[[delegatee retain] autorelease];
// Create delegater
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:delegatee
cancelButtonTitle:cancelButtonTitle
otherButtonTitles:otherButtonTitles, nil];
// Attach the Delegate Block class to the Alert View, increase the retain count
objc_setAssociatedObject(alert, _cmd, delegatee, OBJC_ASSOCIATION_RETAIN);
// Display the alert
[alert show];
return alert;
}
#end
I like that this doesn't add anything on top of NSObject and things are a little more separated. It's attaching to the instance via the address of the function.
I had a similar problem and chose your option 2, but with the 2 small additions:
Explicitly marking the delegate it implements like this:
#interface __DelegateBlock:NSObject <BlocksDelegate>
Check to ensure the callback is not nil before calling:
if (callbackBlock != nil) {
callbackBlock(buttonIndex);
}
Here's what I did:
typedef void(^EmptyBlockType)();
#interface YUYesNoListener : NSObject <UIAlertViewDelegate>
#property (nonatomic, retain) EmptyBlockType yesBlock;
#property (nonatomic, retain) EmptyBlockType noBlock;
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock;
#end
#implementation YUYesNoListener
#synthesize yesBlock = _yesBlock;
#synthesize noBlock = _noBlock;
- (id) initWithYesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
self = [super init];
if (self)
{
self.yesBlock = [[yesBlock copy] autorelease];
self.noBlock = [[noBlock copy] autorelease];
}
return self;
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0 && self.noBlock)
self.noBlock();
else if (buttonIndex == 1 && self.yesBlock)
self.yesBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
- (void) alertViewCancel:(UIAlertView *)alertView
{
if (self.noBlock)
self.noBlock();
[_yesBlock release];
[_noBlock release];
[alertView release];
[self release];
}
+ (void) yesNoWithTitle:(NSString*)title message:(NSString*)message yesBlock:(EmptyBlockType)yesBlock noBlock:(EmptyBlockType)noBlock
{
YUYesNoListener* yesNoListener = [[YUYesNoListener alloc] initWithYesBlock:yesBlock noBlock:noBlock];
[[[UIAlertView alloc] initWithTitle:title message:message delegate:yesNoListener cancelButtonTitle:#"No" otherButtonTitles:#"Yes", nil] show];
}
#end

Objective C Blocks in an Array Passed To Subclass of UIAlertView

(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:
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];
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.
You might find 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];
Static library, easy to include with your project using an Xcode workspace.

Make UIAlertView blocking

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.