Extending action sheets and alert views with blocks

This small example adds practical benefits to previous post on using associated objects in Objective-C categories.

Goal of this tutorial is to make UIActionSheet and UIAlertView simplier by avoiding usage of delegates and adding button tap handlers directly on creation with usage of Objective-C blocks. And we’ll also avoid subclassing.

So, let’s define our new API for action sheets.

typedef void (^ASActionSheetButtonHanlder)();

typedef NS_ENUM(NSUInteger, ASActionSheetButtonType)
{
    ASActionSheetButtonTypeDefault     = 0,
    ASActionSheetButtonTypeDestructive = 1,
    ASActionSheetButtonTypeCancel      = 2
};

@interface UIActionSheet (BlockHandlers) <UIActionSheetDelegate>

-(id)initWithTitle:(NSString *)title;

-(void)addButtonWithTitle:(NSString*)title
               buttonType:(ASActionSheetButtonType)buttonType
                  handler:(ASActionSheetButtonHanlder)block;


@end

We’ve defined parameterless blocks for button tap handler. These blocks would be executed when user taps on specific button. Each button should be added with our new method, supplying title, type (should it be normal, destructive or cancel button) and handler.

UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"Action test"];

[actionSheet addButtonWithTitle:@"Destructive"
                     buttonType:ASActionSheetButtonTypeDestructive
                        handler:^{
                            NSLog(@"Destructive button tapped");
                        }];
[actionSheet addButtonWithTitle:@"Normal 1"
                     buttonType:ASActionSheetButtonTypeDefault
                        handler:^{
                            NSLog(@"Normal 1 button tapped");
                        }];
[actionSheet addButtonWithTitle:@"Normal 2"
                     buttonType:ASActionSheetButtonTypeDefault
                        handler:^{
                            NSLog(@"Normal 2 button tapped");
                        }];
[actionSheet addButtonWithTitle:@"Cancel"
                     buttonType:ASActionSheetButtonTypeCancel
                        handler:^{
                            NSLog(@"Cancel button tapped");
                        }];
[actionSheet showInView:self.view];

Action Sheet Example
Usage is simple. Now let’s take a look on implementation details.

@implementation UIActionSheet (BlockHandlers) 

-(NSMutableDictionary *)handlers
{
    NSMutableDictionary *_handlers = objc_getAssociatedObject(self, @selector(handlers));
    if (!_handlers)
    {
        _handlers = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, @selector(handlers), _handlers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return _handlers;
}

-(id)initWithTitle:(NSString *)title
{
    return [self initWithTitle:title
                      delegate:self
             cancelButtonTitle:nil
        destructiveButtonTitle:nil
             otherButtonTitles:nil];
}

-(void)addButtonWithTitle:(NSString*)title
               buttonType:(ASActionSheetButtonType)buttonType
                  handler:(ASActionSheetButtonHanlder)block
{
    self.delegate = self;
    NSInteger index = [self addButtonWithTitle:title];
    if (block)
    {
        self.handlers[@(index)] = [block copy];
    }
    switch (buttonType)
    {
        case ASActionSheetButtonTypeDestructive:
            self.destructiveButtonIndex = index;
            break;
            
        case ASActionSheetButtonTypeCancel:
            self.cancelButtonIndex = index;
            break;
            
        default:
            break;
    }
}

-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    ASActionSheetButtonHanlder block = self.handlers[@(buttonIndex)];
    if (block)
    {
        block();
    }
}

@end

We’re adding lazily-instanciated property hanlders which will hold handlers (we’ll use button index as a key in dictionary). Since we can’t add instance variables with category, we’re using associated object to store data. ARC will take care of memory management.

We’re also adding new initializer to simplify the code. We’re setting UIActionSheet as its own delegate, so we could call handlers when user taps the button.

We’re using standard UIActionSheet‘s method -addButtonWithTitle: to add button and get back button index (this index is also used to set destructive and cancel buttons). And we’re using buttonIndex parameter of delegate method -actionSheet:clickedButtonAtIndex: to find proper handler. Very simple and effective.

So, calling this UIActionSheet does not require implementing delegate methods. And, most important thing is that you don’t have to differentiate between several action sheets on the same controller. Delegate approach will require you to use tags, or compare UIActionSheet objects, while here you just set proper handling code just where you initialize action sheet.

The same approach is used in UIAlertView category.
Alert View Example

Source code for this tutorial is available on GitHub. You can use it freely under terms of MIT License.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s