Thursday, June 10, 2010

UIKeyboardTypeNumberPad, the DONE button and iOS 4

The post you are about to read is fairly technical and is mostly written for the sake of the iPhone development community, so if you're just a fan of Meters or a friend of mine (you too, Mom), feel free to ignore this post.

One of Apple's big shortcomings in the iPhone SDK is input methods. Apple gives developers very little support in terms of custom input methods, which is why it's so frustrating that the 10-button numeric keyboard that is provided in the SDK is missing something as crucial as a "DONE" button.


Thanks to the work of a development team called Neoos, many app developers found a workaround to this issue by adding a custom button on top of the default numeric keyboard. But in iOS 4, this method stops working. I did some digging and found out everything I could about the issue and want to share it with any developers who might be looking for the same solution I am.
If you're reading this I'm assuming you understand how Neoos' solution to the problem worked. In iPhone OS 3, when a UIKeyboardWillShowNotification was sent, their custom method iterated through all the subviews on the screen to find the keyboard, and then added a custom button on top of it.

The problem is this: in iOS 4 (at least, in the Gold Master Seed available to developers as of a few days ago), the keyboard view is not instantiated by the time the UIKeyboardWillShowNotification is sent, so it's impossible to add the custom button to it.

The best solution I've discovered is easy to implement but doesn't provide the same end result that we had in OS 3. By switching from the UIKeyboardWillShowNotification to the UIKeyboardDidShowNotification, the custom method which searches for the keyboard and adds the button isn't called until the keyboard is already on screen. This means that it is guaranteed to find the keyboard view and add the done button. The disadvantage will only be noticed by particularly observant users: as the keyboard slides into place, the bottom left button is still blank, and once the keyboard is in place, the "DONE" button simply appears

For now, this seems to be the best solution available to us as developers. If anyone manages to find a better solution, I'd love to hear it, and I hope that the information here was helpful in finding that solution.


18 comments:

  1. i can't do that.. can you explain better?
    actually i'm doing this

    in view did load
    [[NSNotificationCenter defaultCenter] addObserver: self
    selector: @selector(keyboardDidShowOrHide:)
    name: UIKeyboardDidShowNotification object:nil];

    then the method

    - (void) keyboardDidShowOrHide : (id) sender {
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 363, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    [doneButton setBackgroundImage:[UIImage imageNamed:@"doneup.png"] forState:UIControlStateNormal];
    [doneButton setBackgroundImage:[UIImage imageNamed:@"donedown.png"] forState:UIControlStateHighlighted];
    [doneButton addTarget:self action:@selector(doneButton) forControlEvents:UIControlEventTouchUpInside];
    UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
    UIView* keyboard;
    for(int i=0; i<[tempWindow.subviews count]; i++) {
    keyboard = [tempWindow.subviews objectAtIndex:i];

    // keyboard view found; add the custom button to it
    if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES)
    [keyboard addSubview:doneButton];
    }
    }

    thank you

    ReplyDelete
  2. You can actually add the button before the keyboard shows, if you schedule it on the next run loop pass (performSelector with delay 0). Example code: http://gist.github.com/454844

    ReplyDelete
  3. if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES


    no longer works in os4 Anyone found out what the new name is?

    ReplyDelete
  4. UIKeyboardDidShowNotification did not work with " hasPrefix:@"<UIKeyboard"" use UITextEffectsWindow

    ReplyDelete
  5. or u can use @"<UIPeripheralHostView"

    ReplyDelete
  6. Thanks a ton saved me a lot of efforts

    ReplyDelete
  7. but the Done button disappears when I change my device
    orientation, and I still need it

    ReplyDelete
  8. @"<UIPeripheralHostView" works okay but, the button is also added to the alphabet keyboard. How to avoid this and only add the Done button to the NumericKeyPad?

    ReplyDelete
  9. Esben,

    do you have a fix for this "zombie" doneButton???

    ReplyDelete
  10. A fade effect could visually enhance the user experience rather than the Done button suddenly appearing :)

    ReplyDelete
  11. I can't find anything of that code.Done button is not shown.I test it on ios 4.2.I test it with debugger control is going to subview part at the end of the code but does not show anything.
    Any kind of help.
    thanks

    ReplyDelete
  12. Works for me on 4.3 both device and simulator. I add the observer in the editingDidBegin and remove in editingDidEnd for the field; this way the button is shown only when certain field is beind edited.

    ReplyDelete
  13. I won't accept my username and password so I can't upload logs, any ideas

    ReplyDelete
  14. Thanks for your help! Mine finally worked. Posting the code here in case it helps anyone else:


    - (void)keyboardDidShow:(NSNotification *)note {
    // create custom button
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 163, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    [doneButton setBackgroundImage:[UIImage imageNamed:@"doneup.png"] forState:UIControlStateNormal];
    [doneButton setBackgroundImage:[UIImage imageNamed:@"donedown.png"] forState:UIControlStateHighlighted];
    [doneButton addTarget:self action:@selector(doneButton) forControlEvents:UIControlEventTouchUpInside];

    // locate keyboard view
    UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
    UIView* keyboard;
    for(int i=0; i<[tempWindow.subviews count]; i++) {
    keyboard = [tempWindow.subviews objectAtIndex:i];
    // keyboard view found; add the custom button to it
    if([[keyboard description] hasPrefix:@"<UIPeripheralHostView"] == YES)
    {
    [keyboard addSubview:doneButton];
    return;
    }
    }
    }

    ReplyDelete
  15. Hey Connor, I have found that it is still possible to use KeyboardWillShow in iOS 4 and plus. Have a look at this snippet: https://gist.github.com/454844. Here goes some comments of the author, Henrik N: "There were two issues:

    UIKeyboardWillShowNotification is sent before the keyboard view exists, but it you schedule your code to run on the next run loop pass, they do exist. So you don't have to bother with DidShow which is visually less pleasing.

    On iOS 4 the UIKeyboard view was elsewhere in the view hierarchy."

    Hope this could help!

    ReplyDelete
  16. Thanks for the trick. Works great.

    ReplyDelete
  17. https://medium.com/@chan0123/missing-done-button-in-ios-d74810a409b1 May be this this link will help full for u

    ReplyDelete