85

So, the numpad keyboard doesn't come with a 'Done' or 'Next' button by default so I'd like to add one. In iOS 6 and below there were some tricks to add a button to the keyboard but they don't seem to be working in iOS 7.

First I subscribe to the keyboard showing notification

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow:)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

Then I try to add a button when the keyboard shows up:

- (void)keyboardWillShow:(NSNotification *)note 
{
    // create custom button
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeSystem];
    doneButton.frame = CGRectMake(0, 50, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    [doneButton setTitle:@"Done" forState:UIControlStateNormal];
    [doneButton addTarget:self action:@selector(dismissKeyboard) 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:@"UIKeyboard"] == YES)
        [keyboard addSubview:doneButton];
    }
}

But the for loop doesn't run because it doesn't find any subviews. Any suggestions? I couldn't find any solutions for iOS7 so is there a different way I'm supposed to be doing this?

Edit: Thanks for all the suggestions for toolbars guys but I'd rather not go down that route as I'm quite space poor (and it is kind of ugly).

Andrey Gordeev
  • 30,606
  • 13
  • 135
  • 162
George McKibbin
  • 1,291
  • 1
  • 11
  • 16

9 Answers9

189

The much safer approach is to use a UIToolBar with Done Button as inputAccessoryView.


Sample Code :

UIToolbar *keyboardDoneButtonView = [[UIToolbar alloc] init];
[keyboardDoneButtonView sizeToFit];
UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle:@"Done"
                                                               style:UIBarButtonItemStyleBordered target:self
                                                              action:@selector(doneClicked:)];
[keyboardDoneButtonView setItems:[NSArray arrayWithObjects:doneButton, nil]];
txtField.inputAccessoryView = keyboardDoneButtonView;

Your -doneClicked method should look like this :

- (IBAction)doneClicked:(id)sender
{
    NSLog(@"Done Clicked.");
    [self.view endEditing:YES];
}

Sample Code Swift:

let keyboardDoneButtonView = UIToolbar.init()
keyboardDoneButtonView.sizeToFit()
let doneButton = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Done, 
                                                   target: self, 
                                                   action: Selector("doneClicked:")))    

keyboardDoneButtonView.items = [doneButton]
textFieldInput.inputAccessoryView = keyboardDoneButtonView

Your -doneClicked method should look like this :

func doneClicked(sender: AnyObject) {
  self.view.endEditing(true)
}
Nykholas
  • 503
  • 1
  • 6
  • 14
Bhavin
  • 27,155
  • 11
  • 55
  • 94
  • I might have to end up doing this. I don't really like how much space it takes up. – George McKibbin Nov 25 '13 at 12:40
  • 3
    @GeorgeMcKibbin: The space should not be the issue here as it will take that space only while you are typing. Also, according to me this approach is far better than messing up with Keyboard that usually Apple does not like. – Bhavin Nov 25 '13 at 13:31
  • When I do this, I only get the toolbar at the very bottom of my screen and the keyboard no longer appears. Thoughts? – Chris Nov 07 '14 at 02:54
  • great answer, just one tidbit, arrayWithObjects is unspokenly deprecated in favor of literals: [NSArray arrayWithObjects:doneButton, nil] => @[doneButton] – Austin Nov 09 '14 at 20:29
  • This works great but when an iPhone 6+ starts in portrait and then is rotated to horizontal the Done button no longer functions, any idea? – Fred Faust Apr 24 '15 at 00:11
  • this is the correct way of doing it. Overlaying a button over the keyboard is a bad idea because the layout can change with future versions of IOS. – Radu Simionescu Sep 09 '15 at 08:13
  • 1
    from iOS 8.0 `UIBarButtonItemStyleBordered` is deprecated use `UIBarButtonItemStyleDone` or `UIBarButtonItemStylePlain` – Nazir Feb 29 '16 at 11:57
134

Even easier way:

Swift 3.0 and above:

func addDoneButton() {
    let keyboardToolbar = UIToolbar()
    keyboardToolbar.sizeToFit()
    let flexBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
        target: nil, action: nil)
    let doneBarButton = UIBarButtonItem(barButtonSystemItem: .done,
        target: view, action: #selector(UIView.endEditing(_:)))
    keyboardToolbar.items = [flexBarButton, doneBarButton]
    textField.inputAccessoryView = keyboardToolbar
}

Swift 2.3 and below:

func addDoneButton() {
    let keyboardToolbar = UIToolbar()
    keyboardToolbar.sizeToFit()
    let flexBarButton = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace,
        target: nil, action: nil)
    let doneBarButton = UIBarButtonItem(barButtonSystemItem: .Done,
        target: view, action: #selector(UIView.endEditing(_:)))
    keyboardToolbar.items = [flexBarButton, doneBarButton]
    textField.inputAccessoryView = keyboardToolbar
}

Objective C:

- (void)addDoneButton {
    UIToolbar* keyboardToolbar = [[UIToolbar alloc] init];
    [keyboardToolbar sizeToFit];
    UIBarButtonItem *flexBarButton = [[UIBarButtonItem alloc]
    initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
    target:nil action:nil];
    UIBarButtonItem *doneBarButton = [[UIBarButtonItem alloc]
    initWithBarButtonSystemItem:UIBarButtonSystemItemDone
    target:self.view action:@selector(endEditing:)];
    keyboardToolbar.items = @[flexBarButton, doneBarButton];
    self.textField.inputAccessoryView = keyboardToolbar;
}

EDIT:

I've created a useful library called DCKit, which already have the toolbar out of the box:

Done toolbar above keyboard in iOS (with using DCKit library)

It also has many other cool features.

Community
  • 1
  • 1
Andrey Gordeev
  • 30,606
  • 13
  • 135
  • 162
  • 1
    It looks like to me you added a flex bar button to Bhavin's answer from 1 year ago as a new answer so I could see why someone down voted it. Maybe I missed something here as well? – Mark McCorkle Aug 07 '14 at 12:25
  • 2
    Yes, I don't use `initWithTitle:@"Done"`, I use `initWithBarButtonSystemItem:UIBarButtonSystemItemDone` instead. This will return standard Apple's Done bar button. Moreover, it'll be already localized – Andrey Gordeev Aug 07 '14 at 14:18
  • 3
    This should be added as an improvement (comment) to the previously correct answer IMO or expect down votes. A new answer should involve a different approach to the original question, not an improvement to an existing question. Nonetheless, thank you for the improvement. ;-) – Mark McCorkle Aug 08 '14 at 14:00
  • 4
    No, I don't think so. Comments are not supposed to be used for writing code :) – Andrey Gordeev Aug 09 '14 at 03:50
26

This is a simple way of projecting a done button in iOS7 num-keypad. In the below delegate method of UITextField, add a notification for keyboard show.

-(void)textFieldDidBeginEditing:(UITextField *)textField {
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillShow:)
                                                 name:UIKeyboardWillShowNotification
                                               object:nil];
}

Now implement the method keyboardWillShow as below. Here we need to take extra care for iOS7.

- (void)keyboardWillShow:(NSNotification *)note {
    // create custom button
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 163, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    [doneButton setImage:[UIImage imageNamed:@"doneButtonNormal.png"] forState:UIControlStateNormal];
    [doneButton setImage:[UIImage imageNamed:@"doneButtonPressed.png"] forState:UIControlStateHighlighted];
    [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];
    
    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
        dispatch_async(dispatch_get_main_queue(), ^{
            UIView *keyboardView = [[[[[UIApplication sharedApplication] windows] lastObject] subviews] firstObject];
            [doneButton setFrame:CGRectMake(0, keyboardView.frame.size.height - 53, 106, 53)];
            [keyboardView addSubview:doneButton];
            [keyboardView bringSubviewToFront:doneButton];
            
            [UIView animateWithDuration:[[note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]-.02
                                  delay:.0
                                options:[[note.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]
                             animations:^{
                                 self.view.frame = CGRectOffset(self.view.frame, 0, 0);
                             } completion:nil];
        });
    } else {
        // locate keyboard view
        dispatch_async(dispatch_get_main_queue(), ^{
            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];
            }
        });
    }
}

Now add this macro to suitable header to detect the SYSTEM_VERSION

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
pkamb
  • 33,281
  • 23
  • 160
  • 191
Balram Tiwari
  • 5,657
  • 2
  • 23
  • 41
  • 1
    Thanks, this is what I wanted :) Unfortunately If there was already a keyboard on screen and then you switch to a field that needs a numpad keyboard then keyBoardWillShow doesn't get called. But thanks, a step in the right direction haha. – George McKibbin Nov 27 '13 at 13:35
  • SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO why not NSFoundationVersionNumber>NSFoundationVersionNumber_iOS_6_0? And I test it, NSFoundationVersionNumber_iOS_5_0 is better – govo Apr 13 '14 at 07:27
  • dispatch_async is not the most reliable method to hack into keyboard here. :( – pronebird May 08 '14 at 15:37
  • @Andy : Kindly Provide more justification for unreliability of dispatch_async or else the most reliable method to use this with this approach. You are free to edit the answer. – Balram Tiwari May 08 '14 at 16:24
  • @BalramTiwari I wish I could, but after hacking for a while it seems to me that Apple moved keyboard initialisation after all public notifications fired. I mean, generally my suggestion here is to stay away from hacking keyboard, even though I see the benefits from UX perspective. – pronebird May 08 '14 at 18:01
  • @BalramTiwari dispatch_async solutions are easy to break in future because they depend on Runloop. – pronebird May 08 '14 at 18:03
  • 7
    in iOS8 this done button is not hiding, After dismissal of keyboard. – Hemant Chittora Sep 19 '14 at 09:33
  • 2
    This answer, albeit clever, was bound to break. – SwiftArchitect Feb 09 '16 at 23:22
13

Just building on answers above with the Swift version since I had to translate it:

@IBOutlet weak var numberTextField: UITextField!

override func viewDidLoad() {
    addDoneButtonTo(numberTextField)
}

// MARK: Done for numberTextField

private func addDoneButtonTo(textField: UITextField) {
    let flexBarButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
    let doneBarButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: "didTapDone:")
    let keyboardToolbar = UIToolbar()
    keyboardToolbar.sizeToFit()
    keyboardToolbar.items = [flexBarButton, doneBarButton]
    textField.inputAccessoryView = keyboardToolbar
}

func didTapDone(sender: AnyObject?) {
    numberTextField.endEditing(true)
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Arnaud
  • 17,268
  • 9
  • 65
  • 83
4

You can add a button to the keyboard's input accessory view

myTextField.inputAccessoryView =_inputView;

input accessory view is a view that comes over the keyboard always and dismiss with the [textfield resignFirstResponder]

put done over the input view and perform resign first responder of the textfields.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Himanshu Gupta
  • 207
  • 1
  • 6
3
  1. register the controller to the notification
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // Keyboard events
    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow:)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}
  1. don't forget to remove the controller from the notification centre
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self.view endEditing:YES];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
  1. implement keyboard notification handlers
- (void)keyboardWillShow:(NSNotification *)notification {

    // create custom button
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 107, 106, 53);
    [doneButton setTitle:@"Done" forState:UIControlStateNormal];
    [doneButton addTarget:self  action:@selector(doneButton:)forControlEvents:UIControlEventTouchUpInside];
 
   // save the reference to the button in order to use it in keyboardWillHide method
   self.donekeyBoardBtn = doneButton;

   // to my mind no need to search for subviews
   UIWindow *windowContainigKeyboard = [[[UIApplication sharedApplication] windows]  lastObject];
   [windowContainigKeyboard addSubview:self.donekeyBoardBtn];
   self.donekeyBoardBtn.frame = CGRectMake(0., CGRectGetHeight(w.frame) -  CGRectGetHeight(self.donekeyBoardBtn.frame), CGRectGetWidth(self.donekeyBoardBtn.frame), CGRectGetHeight(self.donekeyBoardBtn.frame));
}

- (void)keyboardWillHide:(NSNotification *)notification {

    [self.donekeyBoardBtn removeFromSuperview];
}
  1. implement done button action
- (void)doneButton:(id)sender {
    // add needed implementation
    [self.view endEditing:YES]; 
}

pkamb
  • 33,281
  • 23
  • 160
  • 191
loloa
  • 31
  • 2
  • I implemented your answer very similar to what I have to do. Thanks. But button do not come as an animated object, when keyboard show. – Arpit B Parekh Nov 07 '16 at 10:45
2

Just use

yourTextField.inputAccessoryView

hope you help

alvarodoune
  • 921
  • 10
  • 13
2

You do need to detect whether you are on a phone or iPad since the iPad implements a return key on the "number" pad

0

Keyboard view could be found hasPrefix:@"UIKeyboard", the button can not be added as a subview. Here's my solution: enter link description here

anarchist
  • 23
  • 4