Reputation: 7759
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
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:
#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
#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
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
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
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