Subclassing UIResponder

I wanted to make a popup date picker for my app, like so:

tumblr_inline_o66vl9Vdcv1qfpevs_1280.png

Since I wanted it to slide up onto the screen like the keyboard, my immediate thought was to use an inputView and an inputAccessoryView to construct it.

UITextField has built in support for setting an inputView. Whenever the text field is focussed, the inputView appears in place of the keyboard. However, I didn’t want to use a text field to power it. To summon the date picker, I wanted the user to press a toolbar icon. So how did I do it?

It turns out the inputView and inputAccessoryView properties are defined (as read-only) on UIResponder. This is the superclass of UIView, and defines a bunch of methods related to handling events. Most of these are simply there for subclasses to override.

So I subclassed UIResponder. I gave my subclass a delegate protocol so that it could inform the rest of my app when a date had been chosen. And I redefined nextResponder as a read/write property, so that it could be set to my view controller in order to continue the responder chain.

Here’s the header file:

@class UKTDatePickerResponder;

@protocol UKTDatePickerResponderDelegate 

- (void)datePickerResponderDidDismiss:(UKTDatePickerResponder *)datePicker;

@end

@interface UKTDatePickerResponder : UIResponder

@property (nonatomic, copy) NSDate *date;
@property (nonatomic, weak, readwrite) UIResponder *nextResponder;

@property (nonatomic, weak) id delegate;

@end

To implement the functionality, I had to override inputView and inputAccessoryView to return my custom views. Then I had to override canBecomeFirstResponder and becomeFirstResponder to allow my subclass to become the first responder. Here’s the code of the class:

#import "UKTDatePickerResponder.h"

@implementation UKTDatePickerResponder
{
    UIView *_inputView;
    UIView *_inputAccessoryView;
}

- (UIView *)inputView;
{
    if (!_inputView) {
        UIDatePicker *datePicker = [[UIDatePicker alloc] initWithFrame:CGRectZero];
        [datePicker addTarget:self
                       action:@selector(dateUpdated:)
             forControlEvents:UIControlEventValueChanged];
        datePicker.datePickerMode = UIDatePickerModeDateAndTime;
        datePicker.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"BST"];
        datePicker.minuteInterval = 5;
        datePicker.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
        datePicker.backgroundColor = [UIColor whiteColor];
        _inputView = datePicker;
    }
    return _inputView;
}

- (UIView *)inputAccessoryView;
{
    if (!_inputAccessoryView) {
        UIToolbar *toolbar = [[UIToolbar alloc] init];
        toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(doneButtonPressed:)];
        UIBarButtonItem *nowButton = [[UIBarButtonItem alloc] initWithTitle:@"Now" style:UIBarButtonItemStylePlain target:self action:@selector(nowButtonPressed:)];
        UIBarButtonItem *flexibleSeparator = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
        toolbar.items = @[nowButton, flexibleSeparator, doneButton];
        [toolbar sizeToFit];
        _inputAccessoryView = toolbar;
    }
    return _inputAccessoryView;
}

- (void)dateUpdated:(UIDatePicker *)picker
{
    self.date = picker.date;
}

- (void)doneButtonPressed:(UIBarButtonItem *)sender
{
    [self resignFirstResponder];
    [self.delegate datePickerResponderDidDismiss:self];
}

- (void)nowButtonPressed:(UIBarButtonItem *)sender
{
    self.date = nil;
    [self doneButtonPressed:nil];
}

- (BOOL)canBecomeFirstResponder
{
    return YES;
}

- (BOOL)becomeFirstResponder
{
    [super becomeFirstResponder];
    return YES;
}

- (void)setDate:(NSDate *)date
{
    _date = date;
    ((UIDatePicker*)_inputView).date = date ?: [NSDate date];
}

@end

Finally, how do I use it? In my view controller, I first create an instance of my date picker responder:

self.datePickerResponder = [UKTDatePickerResponder new];
    self.datePickerResponder.nextResponder = self;
    self.datePickerResponder.delegate = self;

Then, to activate it, I simply call becomeFirstResponder in the handler for my toolbar button:

[self.datePickerResponder becomeFirstResponder];

That’s all. At that point, the date picker pops up. When the user has finished picking a date, my view controller will be informed via the delegate method.

Previous
Previous

Please rate my app

Next
Next

On being a creative generalist