Sometimes when you're coding an iPhone app, it's handy to be able to pop up a quick alert view, and get some input from the user. UIAlertView nowadays has support for that, but then you have to set up a delegate and everything that entails, which is a hassle. So i've made a tidy wrapper for it, that nicely handles everything for you in blocks, so you only need the following:

[InputAlert showWithTitle:@"Please give me some input"
                          ok:^(NSString *value) {
    NSLog(@"You typed: %@", value);
} cancel:^{
    NSLog(@"They tapped cancel!");
}];

Memory Management

I've used a neat little trick here for memory management. Since access is through a static method, for convenience to the caller, there's no instance maintained by your view controller or what have you. However, we need an instance for the alert view delegate. But alert view's don't retain their delegates (nor should they), so what to do?

Here's my solution in a nutshell, whenever I hit situations like these: get the runloop to retain it for you! Quite simply, i do performSelector:@selector(timeout) withObject:nil afterDelay:Y with a long delay timeout in the static method, and the runloop will retain my object for me. And if the timeout should be called, it simply cleans up the object ready for deallocation. And the final trick: when the alert view is closed, and the object doesn't need to be retained any more, a simple [[self class] cancelPreviousPerformRequestsWithTarget:self] removes the current object from the run loop.

I think this technique has many uses for times when you want to spin off objects that take care of their own lifecycles, i've used it many times before, what do you think? Neat hack or bad practice?

Implementation

Here's InputAlert.m. Note that i'm using ARC for memory management.

//  InputAlert.m
//  Created by Chris Hulbert on 19/07/12.
//  Copyright (c) 2012 Splinter Software. All rights reserved. MIT license.

#import "InputAlert.h"

@interface InputAlert()
@property (strong) UIAlertView* alert;
@property (copy) InputAlertOk ok;
@property (copy) InputAlertCancel cancel;
@end

@implementation InputAlert

@synthesize ok, cancel, alert;

- (void)cleanup {
    self.ok = nil;
    self.cancel = nil;
    self.alert.delegate = nil;
    [self.alert dismissWithClickedButtonIndex:self.alert.cancelButtonIndex animated:YES];
    self.alert = nil;
    [[self class] cancelPreviousPerformRequestsWithTarget:self]; // Stop the runloop retaining this instance
}

// Used so the runloop has something to call on the off chance they never cancel this alert
- (void)timeout {
    [self cleanup];
}

// Pop up an alert that has an input
+ (void)showWithTitle:(NSString*)title ok:(InputAlertOk)ok cancel:(InputAlertCancel)cancel {
    InputAlert* ia = [[InputAlert alloc] init];
    ia.ok = ok;
    ia.cancel = cancel;
    [ia performSelector:@selector(timeout) withObject:nil afterDelay:600]; // Neat hack so that the runloop will retain the instance for us
    ia.alert = [[UIAlertView alloc] initWithTitle:title
                                          message:nil
                                         delegate:ia
                                cancelButtonTitle:@"Cancel"
                                otherButtonTitles:@"OK", nil];
    ia.alert.alertViewStyle = UIAlertViewStylePlainTextInput;
    [ia.alert show];
}

#pragma mark - Delegate methods

// Called when a button is clicked. The view will be automatically dismissed after this call returns
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == alert.firstOtherButtonIndex) {
        if (self.ok) {
            self.ok([[alertView textFieldAtIndex:0] text]);
        }
        [self cleanup];
    } else {
        if (self.cancel) {
            self.cancel();
        }
        [self cleanup];
    }
}

// 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 {
    if (self.cancel) {
        self.cancel();
    }
    [self cleanup];
}

// Called after edits in any of the default fields added by the style
- (BOOL)alertViewShouldEnableFirstOtherButton:(UIAlertView *)alertView {
    return [[[alertView textFieldAtIndex:0] text] length] > 0;
}

@end

Header

Here's InputAlert.h:

//  InputAlert.h
//  Created by Chris Hulbert on 19/07/12.
//  Copyright (c) 2012 Splinter Software. All rights reserved. MIT license

#import <Foundation/Foundation.h>

typedef void(^InputAlertOk)(NSString* value);
typedef void(^InputAlertCancel)();

@interface InputAlert : NSObject<UIAlertViewDelegate>

+ (void)showWithTitle:(NSString*)title ok:(InputAlertOk)ok cancel:(InputAlertCancel)cancel;

@end

Thanks for reading! If you found this article helpful, do me a favour and check out my upcoming web-app: ScrumFox agile team management. And if you want to get in touch, I'd love to hear from you: chris.hulbert at gmail.