Tutorial: Creating a DONE button on the iPhone Number Pad Keyboard

This post was inspired by this one here (read it now), but needed to be extended / further hacked because:

  • their solution doesn’t play well with other keyboards on your view (imagine you have one UITextField for inputting numbers, and another for inputting text). The done button will still be seen overtop of the normal QWERTY keyboard (= unacceptable).
  • It seemed to crash on Retina hardware  (It also doesn’t provide images for retina)
  • It supports iOS <3.0 which is just a waste of code at this point.

Now, I won’t write a step-by-step tutorial.  I want to discuss the approach and will provide some code snippets.

Setup

  • Create a new Xcode project, a single-view application.  Name it whatever you want.  Use ARC.
  • In your ViewController.h file, create 2 properties for 2 text fields, and in the .m file, don’t forget to synthesize these properties!
@interface ViewController : UIViewController

@property (nonatomic, strong) IBOutlet UITextField *alphaField;
@property (nonatomic, strong) IBOutlet UITextField *numField;

@end
  • In your ViewController.xib file, create 2 text fields in the view, and hook them up to these properties above, and don’t forget to set the delegates of both of them!

Now, the mechanism to make multiple UITextFields cooperate is in the .m file.

#import "ViewController.h"

@interface ViewController ()
{
    UIButton *_doneButton;
}
- (void)doneButton:(id)sender;  // this is the button handler when the done button is pressed
- (void)createDoneButton;  // convenience method
- (void)hideDoneButton;  // this will hide the done button when it's time
- (void)unhideDoneButton;  // etc.

@end

@implementation ViewController
@synthesize alphaField, numField;

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self createDoneButton];
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    _doneButton = nil;

}

- (void)doneButton:(id)sender {

    [self.numField resignFirstResponder];
}

- (void)createDoneButton {
	// create custom button

    if (_doneButton == nil) {

        _doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _doneButton.frame = CGRectMake(0, 163, 106, 53);
        _doneButton.adjustsImageWhenHighlighted = NO;
        [_doneButton setImage:[UIImage imageNamed:@"done_button_default.png"] forState:UIControlStateNormal];
        [_doneButton setImage:[UIImage imageNamed:@"done_button_highlighted.png"] forState:UIControlStateHighlighted];

        [_doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];

        _doneButton.hidden = YES;  // we hide/unhide him from here on in with the appropriate method
    }
}

- (void)hideDoneButton
{
    [_doneButton removeFromSuperview];
    _doneButton.hidden = YES;
}

- (void)unhideDoneButton
{
    // this here is a check that prevents NSRangeException crashes that were happening on retina devices  
    int windowCount = [[[UIApplication sharedApplication] windows] count];
    if (windowCount < 2) {
        return;
    }   

    UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex: 1];
    UIView* keyboard;
    for(int i=0; i<[tempWindow.subviews count]; i++) {
        keyboard = [tempWindow.subviews objectAtIndex:i];
        // keyboard found, add the button

        // so the first time you unhide, it gets put on one subview, but in subsequent tries, it gets put on another.  this is why we have to keep adding and removing him from its superview.

        // THIS IS THE HACK BELOW.  I MEAN, PROPERLY HACKY!
        if([[keyboard description] hasPrefix:@"<UIPeripheralHost"] == YES)
        {
            [keyboard addSubview: _doneButton];
        }
        else if([[keyboard description] hasPrefix:@"<UIKeyboardA"] == YES)
        {
            [keyboard addSubview: _doneButton];
        }
    }
    _doneButton.hidden = NO;
}

- (void)viewWillAppear:(BOOL)animated
{
    /* Listen for keyboard */
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];

    [super viewWillAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
    /* No longer listen for keyboard */
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];

    [super viewWillDisappear:animated];
}

- (void)keyboardWillShow:(NSNotification *)notification
{
    // unused.  
}
- (void)keyboardDidShow:(NSNotification *)notification
{
    if (self.numField.isFirstResponder) {
        [self unhideDoneButton];  // self.numField is firstResponder and the one that caused the keyboard to pop up
    }
    else 
    {
        [self hideDoneButton];
    }
}
- (void)keyboardWillHide:(NSNotification *)notification
{
    // unused
}
- (void)keyboardDidHide:(NSNotification *)notification
{
    // unused
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    NSLog(@"%@ for textField %i", NSStringFromSelector(_cmd), textField.tag); 
    if (textField == self.numField) {
        [self unhideDoneButton];
    }
}

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
    NSLog(@"%@ for textField %i", NSStringFromSelector(_cmd), textField.tag);

    if (textField == self.numField) {
        [self hideDoneButton];
    }

    return YES;
}

@end

So, yes, I could have explained more, but at the end of the day, it’s a hack and it works. Knowing the sequence of when the UITextFieldDelegate method was important to figure out when the done button should hide/show, and it’s important to remember that the UIKeyboard is first available on the keyboardDidShow: notification, so that’s why it’s necessary to check if it was the numberField that triggered that, and at that point unhide the button.

Other than that, I just saved you at least one to two hours of messing around.

I should also get around to posting the images I used. Otherwise you can use the non-retina ones provided in the reference link at the beginning of this post.

Best,
S.

About these ads

8 thoughts on “Tutorial: Creating a DONE button on the iPhone Number Pad Keyboard

  1. You’re very welcome! Don’t forget, as the solution is slightly hacky, you should test it against new iOS releases in case something changes on their side.

  2. Why not check for the keyboard type of the first responder of the current view to see if you need to determine wether or not to show the keyboard?

    • Example code? Please note, the original approach was not mine. I just improved it to be a little more robust. So I presume the OP wouldn’t have gone to such complicated lengths if what you propose would work. That said, I’d be grateful if you’d prototype your solution and post your results. Thanks!

  3. When I use UIKeyBoardWillShowNotification [tempWindow.subviews count] is coming out to be “0″ as keyboard is not yet added , then if i use UIKeyboardDidShowNotification it works fine except that done button image is added with a delay …
    If anyone knows the reason for this , plz submit and also provide solution ..

    • Hi Subodh, like the post states near the end, the keyboard object is not defined on keyboardWillShow, which is why in my implementation that method is empty. This solution in general is hacky, so one can’t expect perfect results. Living with a slight delay in displaying the done button is not the worst user experience in the world, as in theory, your done button will have been allocated already, it’s just a matter of unhiding it when they keyboard appears.

      Did you follow this article exactly? Or did you just skim through it and thought you understood it? It’s one of those ones where you kind of have to follow it exactly or something can go wrong. AND there’s no guarantee that it will work in the future, as functionality is based on a string comparison of an object’s ‘description’ property. Far from cool, but works in the absence of a better solution.

  4. Pingback: ios开发-键盘增强 | Something about Hu Gang

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