wannabegeek

Icon

Things I find interesting – generally programming

UIPickerView as a keyboard view for UITableViewCell

It seems to be a common question on how to create custom a keyboard, or use a UIDatePicker or UIPickerView as the input.
Apple have an example on their website which works well. However this doesn’t work well when you cater for device rotation, everything rotates except for the keyboard.

To alleviate this issue, you could watch for device orientation changes via NSNotificationCenter, and transform the view to the new orientation, but currently there is no easy way to do this if the rotation is to be animated.

The Problem

My specific problem was that I needed various input methods to edit values in UITableViewCells (e.g. Dates, Integers, List of options). I tried various methods of doing this by using a UITextField, but the cursor was visible and I could not find a way of hiding it.

The Solution & How it Works

I created a custom UITableViewCell subclass with a style of UITableViewCellStyleValue1. By adopting the protocol UIKeyInput the keyboard will appear the the view becomes the first responder.

The example I’ll give here is for the UIPicker implementation. But in the example code found in the GitHub repository there are more example for the Integers, Strings & Dates.

Implementing the required methods for UIKeyInput is pretty simple, since we are not handling key strokes.

- (BOOL)hasText {
        return YES;
}
 
- (void)insertText:(NSString *)theText {
}
 
- (void)deleteBackward {
}

For this to work, make sure to provide the following method over-ride, otherwise the view can never become the first responder.

- (BOOL)canBecomeFirstResponder {
        return YES;
}

Assuming self.picker has been allocated and configured with it’s data source in your init method, then just implementing the following will present the picker as the keyboard.

- (UIView *)inputView {
        return self.picker;
}

Handling Device Rotations

Handling device rotation is relatively easy, but it took me a while to figure out why it wasn’t resizing correctly. All you need to do is set the autoresizeMask on the picker view, and everything is handled for you.

self.picker.autoresizingMask = UIViewAutoresizingFlexibleHeight;

Adding the ‘done’ button

Currently there is no way to dismiss the picker apart from making it resignFirstResponder (i.e. selecting another input view). The easiest and most common way is add a toolbar above the input view containing a ‘done’ button for dismissing the keyboard.
The done button action just calls [self resignFirstResponder]

- (UIView *)inputAccessoryView {
	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
		return nil;
	} else {
		if (!inputAccessoryView) {
			inputAccessoryView = [[UIToolbar alloc] init];
			inputAccessoryView.barStyle = UIBarStyleBlackTranslucent;
			inputAccessoryView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
			[inputAccessoryView sizeToFit];
			CGRect frame = inputAccessoryView.frame;
			frame.size.height = 44.0f;
			inputAccessoryView.frame = frame;
 
			UIBarButtonItem *doneBtn =[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done:)];
			UIBarButtonItem *flexibleSpaceLeft = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
 
			NSArray *array = [NSArray arrayWithObjects:flexibleSpaceLeft, doneBtn, nil];
			[inputAccessoryView setItems:array];
		}
		return inputAccessoryView;
	}
}
 
- (void)done:(id)sender {
	[self resignFirstResponder];
}

Conclusion

Obviously I have missed out some fairly fundamental methods in the above example, e.g. the delegate methods from the UIPickerView to update the UITableViewCell content.

Making this class compatible with the iPad is more complex. Since the iPad screen is much larger, the options can (should?) popup in a UIPopover. Take a look at the GitHub repository below, where I have enhanced the classes for iPad usage.

For fully working classes see this repository on GitHub

Category: Cocoa, iOS, iOS5, iPad, iPhone, Programming, UIKit

Tagged:

13 Responses

  1. [...] UIPickerView as a keyboard view for UITableViewCell [...]

  2. Johnny says:

    Thanks for sharing nice code.
    how to write a DependentComponent picker?

  3. tom says:

    Johnny, take a look at the sample code, there is an example in there (SimplePickerInputTableViewCell class)

  4. Jub says:

    Thanks, I’ve been looking for that all over the web!

  5. [...]  Luckily for me there are other tutorial writers out there!  After a lot of research I found this great tutorial then expanded on it to use Core Data. I hope the results are useful to [...]

  6. Salvatore says:

    Thanks for sharing! Nice code.
    As for the class SimplePickerInputTableViewCell/PickerInputTableViewCell, if I need to populate the picker with data that changes depending on some parameters, how do I do?
    Thanks and sorry for my English.. I’m italian !

  7. Wal says:

    Very nice tutorial and thanks for sharing the code. But somehow it won’t work for me (on iOS 5.1.1), it works fine on the simulator, but not on my device (iPhone4S).
    String and integer input work fine, but the pickers do not, they do not even appear on the screen. When I launch the string input first, and then a picker input, the picker appears briefly (during the animation), but disappears once that finishes. I got it to stay by removing some setNeedsLayout, but then any touch on the picker immediately dismisses it, does anyone have the same issue?
    This happens in the sample code.

  8. Wal says:

    Update: I fixed it doing the following, but I do not know exactly why it failed. If you figure it out, I’d like to know too.

    In PickerInputTableViewCell.m I added a global
    BOOL resign = NO; Which I set to YES when the Done button is pressed, and I set it to NO again in
    - (BOOL)becomeFirstResponder;

    Then, in – (BOOL)resignFirstResponder; I replace
    return [super resignFirstResponder];
    with
    if (resign)
    return [super resignFirstResponder];
    else
    return YES;

  9. Wal says:

    It turns out it’s not a full solution, it makes any app crash that wants to resign the first responder (without the done button) while the picker is visible and has that property.

  10. Wal says:

    Pardon the barrage, just thought you may want to know.
    In addition to my previous hack, if I also synthesize the picker, that seems to solve my issues. As far as I can see at least.

  11. yoshimitsu81 says:

    Hello, thanks for sharing your code! This sample is very useful.

    In the iPad simulator I noticed that if you select a StringInputTableViewCell row and after you choose a PickerInputTableViewCell row, the keyboard does not disappear because the textField didn’t resign the first risponder. Oddly, the keyboard disappears correctly if you select a DateInputTableViewCell.
    This is a problem in portrait mode because the available height is reduced by the keyboard and the popovers are not shown correctly.

    However, in the “PickerInputTableViewCell.m” file I tried this solution:

    #import “StringInputTableViewCell.h”

    Inside the “(BOOL)becomeFirstResponder” method I replaced the “for” loop with the following:

    for (UIView *subview in self.superview.subviews) {
    if ([subview isFirstResponder]) {
    [subview resignFirstResponder];
    } else {
    if ([subview isKindOfClass:[StringInputTableViewCell class]]) {
    StringInputTableViewCell *cell = (StringInputTableViewCell *)subview;
    if ([cell.textField isFirstResponder]) {
    [cell.textField resignFirstResponder];
    }
    }
    }
    }

    Probably there are better solutions, for example another “for” loop for the subview’s subviews, however this fix seems to work.

  12. Ai says:

    Is there any way that I can change the value of the field at run time. eg: assign current time value to a date picker field when the screen did load?

Leave a Reply