Tutorial on keyboard animation using AutoLayout

Discussion in 'iOS Programming' started by Duncan C, Jan 20, 2014.

  1. Duncan C, Jan 20, 2014
    Last edited: Jan 24, 2014

    Duncan C macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #1
    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.
     
  2. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #2
    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?
     
  3. Duncan C thread starter macrumors 6502a

    Duncan C

    Joined:
    Jan 21, 2008
    Location:
    Northern Virginia
    #3
    Not that I'm aware of.
     
  4. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #4
    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.
     

Share This Page