25

I'm pretty new to objective C and I'm just trying to figure out if I can use a block or a selector as the UIAlertViewDelegate argument for UIAlertView - and which is more appropriate?

I've tried the following but it just isn't working so I'm not sure if I'm on the right track or not?

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Checked In" 
    message:responseString
    delegate:^(UIAlertView * alertView, NSInteger buttonIndex)
                                                    {
                                                       NSLog(@"Done!");
                                                   } 
    cancelButtonTitle:@"OK" 
    otherButtonTitles: nil];

Thanks!

jb007
  • 316
  • 1
  • 3
  • 11
  • Apple doesn't offer it , but it's a good idea. The way to do it is a subclass. I'll make one real quick and post it. – danh Apr 10 '12 at 02:32
  • danh's answer works but it does not support multiple buttons. See my answer below. – Besi Aug 25 '12 at 16:44

11 Answers11

28

Great idea. Here it is. Just like alert view, except adds a block property that's invoked when the alert is dismissed. (Edit - I've simplified this code since the original answer. Here's what I use now in projects)

//  AlertView.h
//

#import <UIKit/UIKit.h>

@interface AlertView : UIAlertView

@property (copy, nonatomic) void (^completion)(BOOL, NSInteger);

- (id)initWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles;

@end

//
//  AlertView.m

#import "AlertView.h"

@interface AlertView () <UIAlertViewDelegate>

@end

@implementation AlertView

- (id)initWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles {

    self = [self initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil];

    if (self) {
        for (NSString *buttonTitle in otherButtonTitles) {
            [self addButtonWithTitle:buttonTitle];
        }
    }
    return self;
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {

    if (self.completion) {
        self.completion(buttonIndex==self.cancelButtonIndex, buttonIndex);
        self.completion = nil;
    }
}

@end

You can extend this idea to supply blocks for other delegate methods, but the didDismiss is the most common.

Call it like this:

AlertView *alert = [[AlertView alloc] initWithTitle:@"Really Delete" message:@"Do you really want to delete everything?" cancelButtonTitle:@"Nevermind" otherButtonTitles:@[@"Yes"]];

alert.completion = ^(BOOL cancelled, NSInteger buttonIndex) {
    if (!cancelled) {
        [self deleteEverything];
    }
};
[alert show];
danh
  • 62,181
  • 10
  • 95
  • 136
  • 2
    Here's a (fuller) implementation of danh's idea: http://blog.mugunthkumar.com/coding/ios-code-block-based-uialertview-and-uiactionsheet/ – thelaws Apr 10 '12 at 02:56
  • Have you tried to add multiple buttons? `:-)` see my answer below. – Besi Aug 25 '12 at 16:43
  • 1
    Thanks @Besi. Updated the answer to incorporate your code properly handling var args. – danh Aug 25 '12 at 17:05
  • 1
    Unless I'm overlooking something, you're not using the `cancelButtonTitle` parameter. It would also be useful to have the alert itself as an argument to the block (as in the delegate method), so that the button index can be compared with `alert.cancelButtonIndex`. – omz Aug 25 '12 at 17:22
  • Maybe I'm missing your first point, but the cancelButtonTitle is passed through to the native init. As for the self param to the block, I think this is better suited to the delegate pattern than the block. It needs to be a weak copy to avoid a retain cycle. But the cancelButtonIndex as a param might be a good addition. – danh Aug 25 '12 at 17:37
  • Thanks @omz. Passing a bool to indicate cancellation. – danh Aug 25 '12 at 17:41
  • @danh I'm having trouble with the `__bridge` cast. Would it be wrong to `va_args(_arguments, NSString *)`? That compiles. Working in ARC. Error is "Error after macro substitution: __bridge requires CF pointer type instead of 'id'" – nmr Mar 20 '13 at 22:14
  • It looks like I accepted somebody's edit that contained the cast. Maybe it was pre-ARC. I'll re-edit now. – danh Mar 21 '13 at 00:12
  • Category for UIAlertView with block. https://github.com/GuyKahlon/UIAlertView-CompletionBlocks.git – Guy Kahlon Apr 14 '14 at 18:06
  • Looking into passing a data object into the alert so that it can be processed with the block. – Alex Zavatone May 12 '15 at 18:00
2

One must use UIAlertController for that approach as Apple document says

A UIAlertController object displays an alert message to the user. This class replaces the UIActionSheet and UIAlertView classes for displaying alerts. After configuring the alert controller with the actions and style you want, present it using the presentViewController:animated:completion: method.

In addition to displaying a message to a user, you can associate actions with your alert controller to give the user a way to respond. For each action you add using the addAction: method, the alert controller configures a button with the action details. When the user taps that action, the alert controller executes the block you provided when creating the action object. Apple Docs.

UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"My Alert"


       message:@"This is an alert."
                                   preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
       handler:^(UIAlertAction * action) {}];

    [alert addAction:defaultAction];
    [self presentViewController:alert animated:YES completion:nil];
ZaEeM ZaFaR
  • 1,508
  • 17
  • 22
1

Check out this UIAlertView-Blocks category on github. I use this and it works well.

Michael Frederick
  • 16,664
  • 3
  • 43
  • 58
1

This is an update to danh's implementation, which is incomplete because it is not possible to add multiple buttons. Passing a va_list to a function is a little tricky :-)

So you could do this, in order to be able to add multiple buttons to the UIAlertView:

- (id)initWithTitle:(NSString *)title message:(NSString *)message completion:(void (^)(NSInteger buttonIndex))completion cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {

    self = [super initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil ];


    if(self){
        _completion = completion;

        va_list _arguments;
        va_start(_arguments, otherButtonTitles);

        for (NSString *key = otherButtonTitles; key != nil; key = (__bridge NSString *)va_arg(_arguments, void *)) {
                [self addButtonWithTitle:key];
        }
        va_end(_arguments);

    }

    return self;
}

Update: There might be a better way of passing the va_list to super. I would like to mention that to me va_lists have something mystic to them :-)

Besi
  • 22,579
  • 24
  • 131
  • 223
  • 2
    That's a good catch. Thanks for the va arg tips. I'll edit my answer in case this one is missed. – danh Aug 25 '12 at 16:59
1

Awesome idea. I have just completed exactly your idea using Category pattern, no subclass, call directly UIAlertView. Please follow these steps:

  1. Category UIAlertView in .h file

    @interface UIAlertView (AlertWithBlock)
    
       - (void (^)(UIAlertView *alertView, NSInteger buttonIndex))delegateBlock;
       - (void)setDelegateBlock:(void (^)(UIAlertView *alertView, NSInteger buttonIndex))delegateBlock;
    
       + (id)alertWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString   *)cancelButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles delegate:(void (^)(UIAlertView *alertView, NSInteger buttonIndex))delegate;
    
    @end
    
  2. In .m file

    @implementation UIAlertView(AlertWithBlock)
    
    static char const *delegateBlockTagKey = "delegateBlockTagKey";
    
    - (void (^)(UIAlertView *alertView, NSInteger buttonIndex))delegateBlock
    {
        return objc_getAssociatedObject(self, delegateBlockTagKey);
    }
    
    - (void)setDelegateBlock:(void (^)(UIAlertView *alertView, NSInteger buttonIndex))delegateBlock
    {
        objc_setAssociatedObject(self, delegateBlockTagKey, delegateBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    + (id)alertWithTitle:(NSString *)title message:(NSString *)message cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSArray *)otherButtonTitles delegate:(void (^)(UIAlertView *alertView, NSInteger buttonIndex))delegate
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil];
        alert.delegate = alert;
        alert.delegateBlock = [delegate copy];
    
        for (NSString *buttonTitle in otherButtonTitles)
        {
            [alert addButtonWithTitle:buttonTitle];
        }
    
        [alert show];
    
        return alert;
    }
    
    #pragma mark - Delegate
    -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
    {
        if (alertView.delegateBlock)
        {
            alertView.delegateBlock(alertView, buttonIndex);
        }
    }
    
    @end
    
  3. Call like this

    [UIAlertView alertWithTitle:@"Title" message:@"This is a message" cancelButtonTitle:@"Cancel" otherButtonTitles:@[@"Yes",@"No"] delegate:^(UIAlertView *alertView, NSInteger buttonIndex) {
    NSLog(@"Click at button index %ld", buttonIndex);
    }];
    

Hope it help.

Tai Le Anh
  • 268
  • 3
  • 10
1

I have written a simple extension in Swift, hope it helpful

import UIKit

extension UIAlertView {

    func show(completion: (alertView: UIAlertView, buttonIndex: Int) -> Void){
        self.delegate = AlertViewDelegate(completion: completion)
        self.show()
    }

    class func showInput(title: String?, message: String?, cancellable: Bool, completion: (text: String?) -> Void){

        var strOK = NSLocalizedString("OK",comment: "OK")
        var strCancel = NSLocalizedString("Cancel",comment: "Cancel")
        var alert = UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: cancellable ? strCancel : strOK)
        alert.alertViewStyle = UIAlertViewStyle.PlainTextInput
        if(cancellable) {
            alert.addButtonWithTitle(strOK)
        }
        alert.show { (alertView, buttonIndex) -> Void in
            if(cancellable && alertView.cancelButtonIndex == buttonIndex) {
                completion(text: nil)
                return
            }
            completion(text: alertView.textFieldAtIndex(0)?.text)
        }
    }

    private class AlertViewDelegate : NSObject, UIAlertViewDelegate {
        var completion :  (alertView: UIAlertView, buttonIndex: Int) -> Void
        var retainedSelf : NSObject?
        init(completion: (UIAlertView, Int) -> Void ) {
            self.completion = completion
            super.init()

            self.retainedSelf = self
        }

        func alertView(alertView: UIAlertView, didDismissWithButtonIndex buttonIndex: Int) {
            var retain = self
            retain.retainedSelf = nil
            retain.completion(alertView: alertView, buttonIndex: buttonIndex)
        }
    }
}
Thong Doan
  • 187
  • 2
  • 7
0

Here is my implementation, seems like it's similar to most answers here: http://stavash.wordpress.com/2013/01/31/quick-tip-uialertview-with-a-block-callback/

Stavash
  • 14,244
  • 5
  • 52
  • 80
0

Simply use REKit. It's similar to BlocksKit, but it's more powerful.

Kazki
  • 61
  • 3
0

Check out UIAlertView-Blocks category on Github. I wrote this and is very easy to use and works well.

Good luck.

Community
  • 1
  • 1
Guy Kahlon
  • 4,510
  • 4
  • 30
  • 41
0

Here is another useful library to do the same. http://bit.ly/ichalrtvw

Code here: http://bit.ly/ichalertview

arundevma
  • 933
  • 7
  • 24
-2

I had to edit the calling example a bit to stop complier error. Just a small tweak and xcode was happy.

UIAlertViewBlock *alert = [[UIAlertViewBlock alloc] initWithTitle:@"hi"
                                                          message:@"hi there"
                                                       completion:^(BOOL canceled,NSInteger buttonIndex) {
                                                           NSLog(@"canceled=%d", canceled);
                                                           NSLog(@"pressed=%d", buttonIndex);
                                                       }
                                               cancelButtonTitle:@"cancel"
                                                otherButtonTitles:@"ok", nil];
[alert show];
nolimitsdude
  • 631
  • 5
  • 9