Constraints aware soft keyboard compensation on iOS

A while ago Apple introduced constraints-based layouts for Mac OS X and iOS user interface designs. It's a huge and logical step forward from pixel-perfect fully manual laying out of things. The line of devices grows and creating all possible variations of interfaces has become a seriously tedious task.

One piece still missing from the puzzle is the set of tools for proper compensation for the soft keyboard. (It's when the keyboard slides up from the bottom of the screen.) There's a good chance that on a non-scrollable page your text fields at the bottom of it will become hidden by the keyboard. At this point you are on your own.

Recently, I've got exactly same problem and fished for solutions. All I could find were several pieces of code with frame-based calculations. That's when you figure the height of the keyboard that's about to slide out and animate the portion of the screen up by the same amount. It all looks like this:

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

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

- (void)keyboardWillShow:(NSNotification*)aNotification {
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    CGRect aRect = self.view.frame;
    aRect.size.height -= kbSize.height;

    [UIView animateWithDuration: 0.3 animations: ^{
        self.view.frame = aRect
    })
}

- (void)keyboardWillHide:(NSNotification*)aNotification {
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    CGRect aRect = self.view.frame;
    aRect.size.height += kbSize.height;

    [UIView animateWithDuration: 0.3 animations: ^{
        self.view.frame = aRect
    })
}

This approach work great until there's a view you want to also resize or move depending on what you do to your primary target. Say, you have a table above your data entry section that you want to shrink as the data entry section goes up when the keyboard slides out.

idea

And that's just the simplest example, believe me...

With modern constraints based layout, you tell the table view bottom edge to follow the entry section top edge and the entry section bottom edge to stick to the bottom of the container. Now to modify the frame-based functions we need to save the bottom edge constraint of the entry section into the outlet (here inputsSpaceConstraint). And then we can safely modify the keyboardWillShow and keyboardWillHide methods as follows.

- (void)keyboardWillShow:(NSNotification*)aNotification {
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;

    [self.view layoutIfNeeded];
    [UIView animateWithDuration: 0.3 animations: ^{
        self.inputsSpaceConstraint.constant = -kbSize.height;
        [self.view layoutIfNeeded];
    })
}

- (void)keyboardWillHide:(NSNotification*)aNotification {
    [self.view layoutIfNeeded];
    [UIView animateWithDuration: 0.3 animations: ^{
        self.inputsSpaceConstraint.constant = 0;
        [self.view layoutIfNeeded];
    })
}

The idea here is to use the constant field of the constraint to shift the view by the given amount (the height of the keyboard), and then, when we hide the keyboard, return it back to zero (or whatever you are using).

One specific thing to notice is that you absolutely need to call layoutIfNeeded before and in the process of animation to see the animation itself, not just before-and-after. That's how Apple suggests it to be.

comments powered by Disqus