Animating Around the iOS Keyboard

My recent post gave an introduction to iOS animation, showing how to move elements around the screen. This time, I’ll show you how to animate view elements in response to the keyboard appearing and disappearing. We’ll pretend we’re working on a photo-sharing app that has a text field to enter the photo’s description into.

Unfortunately, our text field gets covered up by the keyboard as soon as we tap it:

double
Our app has controls on the bottom of the screen (left), but they are covered up by the keyboard (right) when the user taps the text field.

1. Dismissing the Keyboard

The user has no way to dismiss the keyboard once it’s been shown. It would be nice to be able to tap anywhere else on the screen and have the keyboard go away. To do this, we add a Tap Gesture recognizer to our view, along with a method that dismisses the keyboard:

- (void)viewWillAppear:(BOOL)animated
{
  [self.view addGestureRecognizer:
      [[UITapGestureRecognizer alloc] initWithTarget:self
                                              action:@selector(hideKeyboard:)]];
}

- (IBAction)hideKeyboard:(id)sender {
  [self.view endEditing:YES];
}

2. Notifying

Next we register our view controller as an observer for UIKeyboardWillShowNotification and UIKeyboardWillHideNotification, notifications that are automatically fired by iOS right before the keyboard begins animating onto or off of the screen.

(A couple of other keyboard notifications exist — UIKeyboardDidShowNotification and UIKeyboardDidHideNotification — but those aren’t fired until the keyboard is done animating onto or off of the screen, and by then it will be too late to start the animation that we want.)

When the keyboard is about to pop up, call keyboardWillShow. And when it’s about to go away, call keyboardWillBeHidden. These are methods that we’ll implement ourselves.

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

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillBeHidden:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];

3. Dismissing and Animating

This will be our high-level approach to moving our controls up (and down) with the keyboard:

  1. Before the keyboard pops up, figure out:
    – How tall it’s going to be,
    – How long its animation will take, and
    – What type of animation curve it will use.
  2. Right as the keyboard animation starts, launch an animation that moves our controls up exactly as the keyboard moves up (or down).

All the information we need for #1 is stored in the notification object that’s passed to our observing methods. #2 involves passing that information into an animation block that moves our controls view up or down by the correct amount:

- (void)keyboardWillShow:(NSNotification*)notification
{
  [self moveControls:notification up:YES];
}

- (void)keyboardWillBeHidden:(NSNotification*)notification
{
  [self moveControls:notification up:NO];
}

- (void)moveControls:(NSNotification*)notification up:(BOOL)up
{
  NSDictionary* userInfo = [notification userInfo];
  CGRect newFrame = [self getNewControlsFrame:userInfo up:up];

  [self animateControls:userInfo withFrame:newFrame];
}

- (CGRect)getNewControlsFrame:(NSDictionary*)userInfo up:(BOOL)up
{
  CGRect kbFrame = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
  kbFrame = [self.view convertRect:kbFrame fromView:nil];

  CGRect newFrame = self.controlsView.frame;
  newFrame.origin.y += kbFrame.size.height * (up ? -1 : 1);

  return newFrame;
}

- (void)animateControls:(NSDictionary*)userInfo withFrame:(CGRect)newFrame
{
  NSTimeInterval duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
  UIViewAnimationCurve animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];

  [UIView animateWithDuration:duration
                        delay:0
                      options:animationOptionsWithCurve(animationCurve)
                   animations:^{
                     self.controlsView.frame = newFrame;
                   }
                   completion:^(BOOL finished){}];
}

4. Adding a Few Important Details

There were a couple of tricks that popped up in the code. Did you see them? In getNewControlsFrame, we call convertRect with our keyboard frame. This is necessary to account for differences in the coordinate systems when our device is in landscape vs. portrait mode.

We also made a call to animationOptionsWithCurve, which we haven’t implemented yet. Animation blocks in iOS expect a UIViewAnimationOptions struct, which includes the type of animation curve that will be used (whether the animation happens at an even speed the whole time or speeds up/slows down at any point). However, our keyboard notification only gives us a UIViewAnimationCurve.

To convert the curve struct to the more general options struct we can create a helper method (after noticing from the documentation that the curve options are all shifted left by 16 bits in the options struct):

static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCurve curve)
{
  return (UIViewAnimationOptions)curve << 16;
}

Use arithmetic to solve the problem.

Success!

Controls on topAfter all that, we finally get our control buttons and text field moving up and down properly to stay on top of the keyboard. We had to get past a few hurdles, but in the end we're left with an intuitive and polished UI.
 

Conversation
  • Ankit Shah says:

    Hey there, really great post and nicely explained. I followed for my config but not working. Can you please check this : http://stackoverflow.com/questions/28110611/scrollview-not-animating-up-with-keyboard-ios

  • Thank you for great post!

  • Jim Boulter says:

    This code doesn’t cover the new keyboard’s edge cases with the predictions bar. There’s more to this problem than this code lets on, but this is a good starting point.

  • Ricardo says:

    Works perfectly!
    Thanks ;)

  • Comments are closed.