JoBu1324
JoBu1324

Reputation: 7759

Pause code execution until UIAlertView button is pressed?

One of my methods sends a message to an object (what do you know about that), and expects a BOOL for an answer. However, BOOL answer it is expecting is based on the answer to a UIAlertView created in the receiving object's method. However, the code doesn't pause while waiting for the user to answer the UIAlertView. My problem is: how do I use -alertView:clickedButtonAtIndex in the method's return value?

Here's the code the message runs (in this construction, I was expecting navigateAwayFromTab to change based on the user input in the UIAlertView, but it never gets a chance):

- (BOOL)readyToNavigateAwayFromTab {
    NSLog( @"message received by Medical View");
    navigateAwayFromTab = NO;
    UIAlertView *alert = [[UIAlertView alloc]
           initWithTitle:@"Navigate Away From Tab?"
                 message:@"Navigating away from this tab will save your work."
                delegate:self
       cancelButtonTitle:@"Cancel"
       otherButtonTitles:@"OK", nil
    ];
    [alert show];
    [alert release];
    return navigateAwayFromTab;
}
#define CANCEL 0
#define OK 1
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if( buttonIndex == OK ) navigateAwayFromTab = YES;
}

I've been reading up on the modal UIAlertView debate, and I agree with apple's implementation - at lest as the rule. However, in this case I don't see any way of solving the problem by putting code in -alertView:clickedButtonAtIndex because I don't need to run code based on the UIAlertView, I just need to read the response. Any suggestions on how I can reach my gaol? I've tried a while loop after [alert show] already, but then the alert doesn't even show then, and for a number of reasons I can't use -viewWillDisapear.

Edit

For those viewing this question in the modern ios era, this question pertained to ios 2

Upvotes: 4

Views: 18434

Answers (4)

parsley72
parsley72

Reputation: 9087

Best solution I've find is from Sasmito Adibowo in his blog post Painless UIAlertView. He created BSAlertViewDelegateBlock, a generic UIAlertViewDelegate implementation that allows you to use closures as a delegate handler.

Here's how you use it:

UIAlertView* alert = ...;
BSAlertViewDelegateBlock* alertDelegate = [[BSAlertViewDelegateBlock alloc] initWithAlertView:alert];
alertDelegate.didDismissWithButtonIndexBlock = ^(UIAlertView* alertView, NSInteger buttonIndex)
{
    switch (buttonIndex)
    {
        ...
    }
};
[alert show];

The implementation of BSAlertViewDelegateBlock.h and BSAlertViewDelegateBlock.m are available on GitHub but I've reposted the code here:

BSAlertViewDelegateBlock.h

#import <UIKit/UIKit.h>

/**
Adapts UIAlertViewDelegate protocol into a block-friendly interface.
*/
@interface BSAlertViewDelegateBlock : NSObject<UIAlertViewDelegate>

/**
Designated initializer.
*/
-(id) initWithAlertView:(UIAlertView*) alertView;

@property (nonatomic,strong) void(^clickedButtonAtIndexBlock)(UIAlertView* alertView,NSInteger buttonIndex);
@property (nonatomic,strong) void(^alertViewCancelBlock)(UIAlertView* alertView);
@property (nonatomic,strong) void(^willPresentAlertViewBlock)(UIAlertView* alertView);
@property (nonatomic,strong) void(^didPresentAlertViewBlock)(UIAlertView* alertView);

@property (nonatomic,strong) void(^willDismissWithButtonIndexBlock)(UIAlertView* alertView,NSInteger buttonIndex);
@property (nonatomic,strong) void(^didDismissWithButtonIndexBlock)(UIAlertView* alertView,NSInteger buttonIndex);

@property (nonatomic,strong) BOOL(^alertViewShouldEnableFirstOtherButtonBlock)(UIAlertView* alertView);

@end

BSAlertViewDelegateBlock.m

#import <objc/runtime.h>

#import "BSAlertViewDelegateBlock.h"

const char* const BSAlertViewDelegateBlockKey = "BSAlertViewDelegateBlockKey";

@interface BSAlertViewDelegateBlock ()

@property (nonatomic,weak) UIAlertView* alertView;

@end

@implementation BSAlertViewDelegateBlock

-(void) alertViewDelegateBlock_cleanup
{
UIAlertView* alertView = self.alertView;
// remove all handler blocks in case one of these blocks inadvertendly caused a circular reference to this delegate object.
self.clickedButtonAtIndexBlock = nil;
self.alertViewCancelBlock = nil;
self.willPresentAlertViewBlock = nil;
self.didPresentAlertViewBlock = nil;
self.willDismissWithButtonIndexBlock = nil;
self.didDismissWithButtonIndexBlock = nil;
self.alertViewShouldEnableFirstOtherButtonBlock = nil;
// finally remove this delegate from the alert view
if (alertView.delegate == self) {
alertView.delegate = nil;
objc_setAssociatedObject(alertView, BSAlertViewDelegateBlockKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}

-(id) initWithAlertView:(UIAlertView *)alertView
{
if (self = [super init]) {
self.alertView = alertView;
objc_setAssociatedObject(alertView, BSAlertViewDelegateBlockKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
alertView.delegate = self;
}
return self;
}


#pragma mark UIAlertViewDelegate

// Called when a button is clicked. The view will be automatically dismissed after this call returns
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
void(^clickedButtonAtIndexBlock)(UIAlertView* alertView,NSInteger buttonIndex) = self.clickedButtonAtIndexBlock;
if (clickedButtonAtIndexBlock) {
clickedButtonAtIndexBlock(alertView,buttonIndex);
}
}

// Called when we cancel a view (eg. the user clicks the Home button). This is not called when the user clicks the cancel button.
// If not defined in the delegate, we simulate a click in the cancel button
- (void)alertViewCancel:(UIAlertView *)alertView
{
void(^alertViewCancelBlock)(UIAlertView* alertView) = self.alertViewCancelBlock;
if (alertViewCancelBlock) {
alertViewCancelBlock(alertView);
}
[self alertViewDelegateBlock_cleanup];
}

- (void)willPresentAlertView:(UIAlertView *)alertView
{
void(^willPresentAlertViewBlock)(UIAlertView* alertView) = self.willPresentAlertViewBlock;
if (willPresentAlertViewBlock) {
willPresentAlertViewBlock(alertView);
}
}


- (void)didPresentAlertView:(UIAlertView *)alertView
{
void(^didPresentAlertViewBlock)(UIAlertView* alertView) = self.didPresentAlertViewBlock;
if (didPresentAlertViewBlock) {
didPresentAlertViewBlock(alertView);
}
}


- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
void(^willDismissWithButtonIndexBlock)(UIAlertView* alertView,NSInteger buttonIndex) = self.willDismissWithButtonIndexBlock;
if (willDismissWithButtonIndexBlock) {
willDismissWithButtonIndexBlock(alertView,buttonIndex);
}
}


- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
void(^didDismissWithButtonIndexBlock)(UIAlertView* alertView,NSInteger buttonIndex) = self.didDismissWithButtonIndexBlock;
if (didDismissWithButtonIndexBlock) {
didDismissWithButtonIndexBlock(alertView,buttonIndex);
}
[self alertViewDelegateBlock_cleanup];
}


- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView
{
BOOL(^alertViewShouldEnableFirstOtherButtonBlock)(UIAlertView* alertView) = self.alertViewShouldEnableFirstOtherButtonBlock;
if (alertViewShouldEnableFirstOtherButtonBlock) {
return alertViewShouldEnableFirstOtherButtonBlock(alertView);
}

// default to enable.
return YES;
}


@end

Upvotes: 1

Raphael Schaad
Raphael Schaad

Reputation: 1659

Solution using NSCondition when triggering from a background thread:

// on background thread
condition = [NSCondition new];
questionAnswered = NO;
// display UIAlertView in a method on main thread with -performSelectorOnMainThread:withObject:waitUntilDone:
[condition lock];
while (! questionAnswered) [condition wait];
questionAnswered = NO;
[condition unlock];
[condition release];

// on main thread in delegate method -alertView:clickedButtonAtIndex:
// (do something with choosen buttonIndex)
questionAnswered = YES;
[condition signal];
[condition unlock]

Raphael

Upvotes: 4

Steven Fisher
Steven Fisher

Reputation: 44886

Not only does UIAlertView's show not wait for the user to touch a button, it doesn't even wait for the alert view to fade into view. It returns immediately.

Add a flag to your class. If it's NO, return NO from readyToNavigateAwayFromTab and show your alert. In clickedButtonAtIndex, set the flag so that readyToNavigateAwayFromTab knows to return YES. Still in clickedButtonAtIndex, retry the tab navigation from code.

Upvotes: 10

Alex Reynolds
Alex Reynolds

Reputation: 96994

The [alert show] statement should hold up the application until a response is provided.

Have you made your controller subscribe to the UIAlertViewDelegate protocol? Check if you need to add <UIAlertViewDelegate> to your controller header file, e.g.:

@interface RootViewController : UIViewController <UIAlertViewDelegate, UITabBarDelegate, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate>

You might also make your navigateAwayFromTab variable a property, e.g.:

@interface RootViewController : UIViewController <UIAlertViewDelegate> {
   BOOL navigateAwayFromTab;
}

@property BOOL navigateAwayFromTab;

...

@end

In the implementation:

@implementation RootViewController

@synthesize navigateAwayFromTab;

...

- (void) readyToNavigateAwayFromTab {
   UIAlertView *alert = [[UIAlertView alloc]
       initWithTitle:@"Navigate Away From Tab?"
             message:@"Navigating away from this tab will save your work."
            delegate:self
   cancelButtonTitle:@"Cancel"
   otherButtonTitles:@"OK", nil
  ];
  [alert show];
  [alert release];
}

#define CANCEL 0
#define OK 1    
- (void) alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
   if ([actionSheet.title compare:@"Navigate Away From Tab?"] == NSOrderedSame) {
     if (buttonIndex == OK)
       self.navigateAwayFromTab = YES;
     else
       self.navigateAwayFromTab = NO;
   }
}

Upvotes: 0

Related Questions