Tuesday, February 22, 2011

UIAlertView with blocks, revisited

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

One issue with my UIAlertView with blocks class, here, is that it only supports willDismiss, not any other delegate methods supported by UIAlertViewDelegate. At the time, that was all I needed, but more recently, my requirements changed. (Hint: if you’re presenting a UIActionSheet from within a willDismiss delegate method call, bad things will probably happen, as of iOS 4.2).

So I've updated it to support all the other UIAlertViewDelegate methods. It’s pretty straightforward.

First we make typedefs for each type of block, one corresponding to each UIAlertViewDelegate method.

typedef void (^WillPresentBlock)(void);
typedef void (^DidPresentBlock)(void);
typedef void (^DidCancelBlock)(void);
typedef void (^ClickedButtonBlock)(NSInteger);
typedef void (^WillDismissBlock)(NSInteger);
typedef void (^DidDismissBlock)(NSInteger);

Then we need an instance variable, and a property for each.

@interface ZPAlertView : UIAlertView  {
    WillPresentBlock willPresentBlock;
    DidPresentBlock didPresentBlock;
    DidCancelBlock didCancelBlock;
    ClickedButtonBlock clickedButtonBlock;
    WillDismissBlock willDismissBlock; 
    DidDismissBlock didDismissBlock;
}
@property (nonatomic, copy) WillPresentBlock willPresentBlock;
@property (nonatomic, copy) DidPresentBlock didPresentBlock;
@property (nonatomic, copy) DidCancelBlock didCancelBlock;
@property (nonatomic, copy) ClickedButtonBlock clickedButtonBlock;
@property (nonatomic, copy) WillDismissBlock willDismissBlock; 
@property (nonatomic, copy) DidDismissBlock didDismissBlock;
@end

Note that the properties are set for copy. Retaining a block doesn’t work as you’d expect; we need to make a copy.

We synthesize our properties. No need for custom stuff here.

@synthesize willPresentBlock;
@synthesize didPresentBlock;
@synthesize didCancelBlock;
@synthesize clickedButtonBlock;
@synthesize willDismissBlock; 
@synthesize didDismissBlock;

Since we’ve made our properties copy, we need to make sure we release them in dealloc.

- (void) dealloc
{
    [willPresentBlock release];
    [didPresentBlock release];
    [didCancelBlock release];
    [clickedButtonBlock release];
    [willDismissBlock release];
    [didDismissBlock release];

    [super dealloc];
}

The real magic is in our overridden show method, which doesn’t look very much like magic at all:

- (void) show
{
    self.delegate = self;
    [super show];
}

Finally we have the actual delegate method calls, each of which call the appropriate block. That’s it.

- (void) willPresentAlertView:(UIAlertView *)alertView
{
    if( willPresentBlock != nil ) {
        willPresentBlock();
    }
}

- (void) didPresentAlertView:(UIAlertView *)alertView
{
    if( didPresentBlock != nil ) {
        didPresentBlock();
    }
}

- (void) alertViewCancel:(UIAlertView *)alertView
{
    if( didCancelBlock != nil ) {
        didCancelBlock();
    }
}

- (void) alertView:(UIAlertView*)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if( clickedButtonBlock != nil ) {
        clickedButtonBlock(buttonIndex);
    }
}

- (void) alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if( willDismissBlock != nil ) {
        willDismissBlock(buttonIndex);
    }
}

- (void) alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if( didDismissBlock != nil ) {
        didDismissBlock(buttonIndex);
    }
}

The client code would look like this:

- (void) doLengthyOperationPrompt
{
    enum {
        ButtonIndexCancel = 0,
        ButtonIndexDoItNow,
        ButtonIndexDoItLater
    };
 
    ZPAlertView *anAlert = [[ZPAlertView alloc] initWithTitle:@"Warning!"
        message:@"Would you like to perform a really lengthy operation?"
        delegate:nil
        cancelButtonTitle:@"Nope"
        otherButtonTitles:@"Yeah, sure", @"Meh, maybe later", nil];
    anAlert.didDismissBlock = ^(NSInteger buttonIndex){
        switch( buttonIndex ) {
            case ButtonIndexDoItNow:
                [self performLengthyOperation];
            break;
            case ButtonIndexDoItLater:
                [self scheduleLengthyOperationForLater];
            break;
        }
    };
    [anAlert show];
    [anAlert release];
}

I'll try to put the full source and and example project up somewhere, soon. Any and all feedback is welcome.

Update: It's now up on github, here.

Monday, February 14, 2011

Why Didn't It Come With This? Part 1

Note: This blog is deprecated. @synthesize zach has moved to a new home, at zpasternack.org. This blog entry can be found on the new blog here. Please update your links.

Sometimes I find myself writing the same function over and over, and I wonder, "Why didn't it come with this?" Here's one I did today. I have a UIImage which I want to scale to a different size. Actually, this happens in 3 or 4 places in PuzzleTiles. I finally broke down and made it a category on UIImage. Hopefully you find it useful as well.

// UIImage+Resizing.h

@interface UIImage (Resizing)

- (UIImage*) scaledImageWithSize:(CGSize)newSize;

@end


// UIImage+Resizing.m

#import "UIImage+Resizing.h"

@implementation UIImage (Resizing)

- (UIImage*) scaledImageWithSize:(CGSize)newSize {
    UIGraphicsBeginImageContextWithOptions( newSize, NO, 0.0 );
    CGRect scaledImageRect = CGRectMake( 0.0, 0.0, newSize.width, newSize.height );
    [self drawInRect:scaledImageRect];
    UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext(); 

    return scaledImage;
}

@end