Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Duncan C

macrumors 6502a
Original poster
Jan 21, 2008
853
0
Northern Virginia
When it comes to AutoLayout in iOS 6 and iOS 7, everything you know is wrong.

Using struts and springs, you can just shift your views's centers to make room for the keyboard. No such luck using AutoLayout. There you have to manipulate constraints.

The trick is put everything in your VC that you want to shift up (usually all the contents of your VC except the navigation bar) in a container view, with a top constraint tied to the top layout guide and a bottom constraint tied to the bottom layout guide.

You then add IBOutlets to those constraints so you can change them in code.

Then, you add handlers for the will show keyboard notification and the will hide keyboard notification. You have to do a bunch of math to figure out how much to shift things, and finally you grab the top constraint and subtract from it's "constant" value, and add the same amount to the bottom constraint.

The code I use looks like this:

Code:
  //Set up a notification handler to shift the content view up to make room for the keyboard if the current text field
  //Will be covered by the keyboard.
  showKeyboardNotificaiton = [[NSNotificationCenter defaultCenter] addObserverForName: UIKeyboardWillShowNotification

                   object: nil
                    queue: nil
               usingBlock: ^(NSNotification *note)
  {
    CGRect keyboardFrame;
    NSDictionary* userInfo = note.userInfo;
    //keyboardSlideDuration is an instance variable so we can keep it around to use in the "dismiss keyboard" animation.
    keyboardSlideDuration = [[userInfo objectForKey: UIKeyboardAnimationDurationUserInfoKey] floatValue];

    //Get the animation curve from the user info and convert it
    //from a UIViewAnimationCurve value to a UIViewAnimationOptions value
      
    //keyboardAnimationCurve is an instance variable so we can keep it around to use in the "dismiss keyboard" animation.
    keyboardAnimationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]<<16;

    //Get the size of the keyboard.
    keyboardFrame = [[userInfo objectForKey: UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    
    UIInterfaceOrientation theStatusBarOrientation = [[UIApplication sharedApplication] statusBarOrientation];
    
    CGFloat keyboardHeight;
    
    //The keyboard frame will not refelect the current orietnation. height = width if we're in landscape...
    if UIInterfaceOrientationIsLandscape(theStatusBarOrientation)
      keyboardHeight = keyboardFrame.size.width;
    else
      keyboardHeight = keyboardFrame.size.height;
    
    //Get the bounds of the current text field.
    CGRect fieldFrame = textFieldToEdit.bounds;
    
    //Convert the field's bounds to the coordinates of the VC's content view.
    fieldFrame = [self.view convertRect: fieldFrame fromView: textFieldToEdit];
    
    CGRect contentFrame = self.view.frame;
    
    //Calculate the Y position of the bottom of the input field.
    CGFloat fieldBottom = fieldFrame.origin.y + fieldFrame.size.height;
    
    keyboardShiftAmount= 0;
    
    //If the bottom of the input field is going to be covered by the keyboard...
    if (contentFrame.size.height + contentFrame.origin.y - fieldBottom <keyboardHeight)
    {
      //Figure out how much to shift the container view to expose the input field (plus 5 pixels of "breathing room")
      keyboardShiftAmount = keyboardHeight - (contentFrame.size.height + contentFrame.origin.y - fieldBottom)+5;
      
      //keyboardShiftAmount is an instance variable so we can use it to shift the container view back again when the keyboard disappears.
      
      //Adjust the top and bottom constraints for the container view
      containerTopConstraint.constant -= keyboardShiftAmount;
      containerBottomConstraint.constant += keyboardShiftAmount;
      
      
      //animate the change to the view constraint using
      //the duration and animation curve specified in the keyboard notification.
      [UIView animateWithDuration: keyboardSlideDuration
                            delay: 0
                          options: keyboardAnimationCurve
                       animations:^{
                         [containerView layoutIfNeeded];
                       }
       completion: nil
       ];
     }
  }
];

  //Set up another notification handler to move the content view back down as the keyboard is dismissed.
  hideKeyboardNotificaiton = [[NSNotificationCenter defaultCenter] addObserverForName: UIKeyboardWillHideNotification
                   object: nil
                    queue: nil
               usingBlock: ^(NSNotification *note)
{
  if (keyboardShiftAmount != 0)
    [UIView animateWithDuration: keyboardSlideDuration
                          delay: 0
                        options: keyboardAnimationCurve
                     animations:
     ^{
       //Reverse the changes to the container view's top and bottom constraints
       //from the show keyboard animation above
       containerBottomConstraint.constant -= keyboardShiftAmount;
       containerTopConstraint.constant += keyboardShiftAmount;
       [self.view setNeedsUpdateConstraints];
       [containerView layoutIfNeeded];
     }
                     completion: nil
     ];
}
];

That code uses a number of instance variables to keep track of things, and outlets to the top and bottom constraints (as mentioned above)

It's easier to show a working project using this technique then to try to explain everything.

You can see it working in a project I put up on github today.

UIImageView frame animation with cross-fading.

The project's main purpose is to illustrate UIImageView animation that supports cross-fading, but the keyboard shifting is a side-benefit. I figured it could use it's own tutorial topic, since it is NOT easy to figure out how to do this.
 
Last edited:

ArtOfWarfare

macrumors G3
Nov 26, 2007
9,544
6,042
This is disappointingly complicated. It seems like Apple dropped the ball really badly when they failed to make AutoLayout able to automatically work with the keyboard.

Did they happen to fix this in iOS 8?
 

PhoneyDeveloper

macrumors 68040
Sep 2, 2008
3,114
93
There are quite a few tutorials/forum posts on this topic on various sites. They all use the same basic technique, which I think was explained in a WWDC video. This one is somewhat simpler than Duncan's because the view being adjusted is simpler. That code isn't any more complicated than the previous example from Apple's docs that adjusted the frame or edge insets.

http://www.think-in-g.net/ghawk/blo...yout-an-example-of-keyboard-sensitive-layout/

I assume that UITableViewController does this for you without any configuration.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.