I need make UIAlertView
blocking. Because i have function and i need to return UIAlertView
choice. But problem is that after UIAlertView
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.
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];
}