Random Sequence

How to add a toolbar above the standard iPhone keyboard with next, previous & done buttons, as Mobile Safari does when filling in forms.

Sponsored Links:

Adding a Toolbar with Next & Previous above UITextField Keyboard

Here’s how to add a custom toolbar above the standard iPhone keyboard which animates in and out as the keyboard shows / hides. Sample project included at the bottom.

NOTE: From iOS 3.2, UITextView has an inputAccessoryView property which makes most of this redundant

The important parts are:

Subscribe to the UIWindow notifications UIKeyboardWillShowNotification and UIKeyboardWillHideNotification

These notify the observer of the keyboard’s appearance, size, position and animation details. With this information, we can animate our toolbar into the correct position in sync with the keyboard appearance animation.

The header file:

//
//  TFTestViewController.h
//  TFTest
//
//  Created by Johnnie Walker on 26/01/2010.
//

#import <UIKit/UIKit.h>

@interface TFTestViewController : UIViewController {
  IBOutlet UIToolbar *keyboardToolbar;
  UISegmentedControl *nextPreviousControl;  

  IBOutlet UITextField *textField1;
  IBOutlet UITextField *textField2;
  IBOutlet UITextField *textField3;

  BOOL keyboardToolbarShouldHide;
}

@property (nonatomic, retain) UISegmentedControl *nextPreviousControl;
@property (nonatomic, retain) UIToolbar *keyboardToolbar;

- (IBAction)nextPrevious:(id)sender;
- (IBAction)dismissKeyboard:(id)sender;
- (IBAction)editingChanged:(id)sender;
@end

Creating & showing the toolbar when the keyboard appears:

- (void)keyboardWillShow:(NSNotification *)notification
{ 
  CGPoint beginCentre = [[[notification userInfo] valueForKey:UIKeyboardCenterBeginUserInfoKey] CGPointValue];
  CGPoint endCentre = [[[notification userInfo] valueForKey:UIKeyboardCenterEndUserInfoKey] CGPointValue];
  CGRect keyboardBounds = [[[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey] CGRectValue];
  UIViewAnimationCurve animationCurve = [[[notification userInfo] valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue];
  NSTimeInterval animationDuration = [[[notification userInfo] valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];    

  if (nil == keyboardToolbar) {

    if(nil == keyboardToolbar) {
      keyboardToolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0,0,self.view.bounds.size.width,44)];
      keyboardToolbar.barStyle = UIBarStyleBlackTranslucent;
      keyboardToolbar.tintColor = [UIColor darkGrayColor];

      UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissKeyboard:)];
      UIBarButtonItem *flex = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];

      UISegmentedControl *control = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:
                                           NSLocalizedString(@"Previous",@"Previous form field"),
                                           NSLocalizedString(@"Next",@"Next form field"),                                         
                                           nil]];
      control.segmentedControlStyle = UISegmentedControlStyleBar;
      control.tintColor = [UIColor darkGrayColor];
      control.momentary = YES;
      [control addTarget:self action:@selector(nextPrevious:) forControlEvents:UIControlEventValueChanged];     

      UIBarButtonItem *controlItem = [[UIBarButtonItem alloc] initWithCustomView:control];

      self.nextPreviousControl = control;


      NSArray *items = [[NSArray alloc] initWithObjects:controlItem, flex, barButtonItem, nil];
      [keyboardToolbar setItems:items];
      [control release];
      [barButtonItem release];
      [flex release];
      [items release];      

      keyboardToolbar.frame = CGRectMake(beginCentre.x - (keyboardBounds.size.width/2), 
                         beginCentre.y - (keyboardBounds.size.height/2) - keyboardToolbar.frame.size.height, 
                         keyboardToolbar.frame.size.width, 
                         keyboardToolbar.frame.size.height);        

      [self.view addSubview:keyboardToolbar];   
    }   
  }   

  [UIView beginAnimations:@"RS_showKeyboardAnimation" context:nil]; 
  [UIView setAnimationCurve:animationCurve];
  [UIView setAnimationDuration:animationDuration];

  keyboardToolbar.alpha = 1.0;  
  keyboardToolbar.frame = CGRectMake(endCentre.x - (keyboardBounds.size.width/2), 
                     endCentre.y - (keyboardBounds.size.height/2) - keyboardToolbar.frame.size.height - self.view.frame.origin.y, 
                     keyboardToolbar.frame.size.width, 
                     keyboardToolbar.frame.size.height);

  [UIView commitAnimations];    

  keyboardToolbarShouldHide = YES;
}

Hiding the toolbar when the keyboard disappears, again in sync with the keyboard animation:

- (void)keyboardWillHide:(NSNotification *)notification
{
  if (nil == keyboardToolbar || !keyboardToolbarShouldHide) {
    return;
  } 

  CGPoint endCentre = [[[notification userInfo] valueForKey:UIKeyboardCenterEndUserInfoKey] CGPointValue];
  CGRect keyboardBounds = [[[notification userInfo] valueForKey:UIKeyboardBoundsUserInfoKey] CGRectValue];
  UIViewAnimationCurve animationCurve = [[[notification userInfo] valueForKey:UIKeyboardAnimationCurveUserInfoKey] intValue];
  NSTimeInterval animationDuration = [[[notification userInfo] valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];  

  [UIView beginAnimations:@"RS_hideKeyboardAnimation" context:nil]; 
  [UIView setAnimationCurve:animationCurve];
  [UIView setAnimationDuration:animationDuration];


  keyboardToolbar.alpha = 0.0;
  keyboardToolbar.frame = CGRectMake(endCentre.x - (keyboardBounds.size.width/2), 
                     endCentre.y - (keyboardBounds.size.height/2) - keyboardToolbar.frame.size.height,
                     keyboardToolbar.frame.size.width, 
                     keyboardToolbar.frame.size.height);

  [UIView commitAnimations];
}

Switch between fields using [(UITextField *) becomeFirstResponder:]

Note that this is different to the way Mac OS X works, and not obvious from the documentation. Also note findFirstResponder: is not a standard UIView method. The sample project includes the code for the category on UIView.

- (void)nextPrevious:(id)sender
{

  UIView *responder = [self.view findFirstResponder];   

  if (nil == responder || ![responder isKindOfClass:[GroupTextField class]]) {
    return;
  }

  switch([(UISegmentedControl *)sender selectedSegmentIndex]) {
    case 0:
      // previous
      if (nil != ((GroupTextField *)responder).previousControl) {
        [((GroupTextField *)responder).previousControl becomeFirstResponder];
        DebugLog(@"currentControl: %i previousControl: %i",((GroupTextField *)responder).tag,((GroupTextField *)responder).previousControl.tag);
      }
      break;
    case 1:
      // next
      if (nil != ((GroupTextField *)responder).nextControl) {
        [((GroupTextField *)responder).nextControl becomeFirstResponder];
        DebugLog(@"currentControl: %i nextControl: %i",((GroupTextField *)responder).tag,((GroupTextField *)responder).nextControl.tag);
      }     
      break;    
  }
}

Listen for UITextView’s textFieldShouldBeginEditing: and textFieldShouldEndEditing: delegate methods.

When switching between fields either programatically or by tapping on them, the observer still recieves the keyboard hide / show notifications. Here we keep track when we’re moving to another field to cancel the animation.

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
  keyboardToolbarShouldHide = NO;
  DebugLog(@"textFieldShouldBeginEditing: %@",textField);
  return YES;
}

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
  DebugLog(@"textFieldShouldEndEditing: %@",textField);
  return YES;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
  if (textField == textField1) {
    [textField2 becomeFirstResponder];
  } else if (textField == textField2) {
    [textField3 becomeFirstResponder];
  } else if (textField == textField3) {
    [textField1 becomeFirstResponder];
  }   
  return NO;
}

Use these to avoid animating the toolbar when the keyboard stays on screen.

Download the test project: TFTest (24 KB)

Sponsored Links: