35

Im stuck trying to animate a table view smoothly which has an autolayout contraint. I have a reference to the constraint "keyboardHeight" in my .h and have linked this up in IB. All i want to do is animate the table view with the keyboard when it pops up. Here is my code:

- (void)keyboardWillShow:(NSNotification *)notification
{
    NSDictionary *info = [notification userInfo];
    NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    CGRect keyboardFrame = [kbFrame CGRectValue];
    CGFloat height = keyboardFrame.size.height;

    [UIView animateWithDuration:animationDuration animations:^{
        self.keyboardHeight.constant = -height;
        [self.view setNeedsLayout];
    }];
}

The thing is the animation block is instantaneous and I see white space appear before the keyboard has finished its animation. So basically I see the white background of the view as the keyboard is animating. I cannot make the animation last for as long as the keyboard is animating.

Am i approaching this the wrong way? Thanks in advance!

Stavash
  • 14,244
  • 5
  • 52
  • 80
Marty W
  • 415
  • 1
  • 5
  • 12
  • Hey it's been quite some time but your forgot the animation type: UIViewAnimationOptions animationOptions = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]; – marsbear Aug 24 '15 at 18:47

4 Answers4

52

Try it this way:

self.keyboardHeight.constant = -height;
[self.view setNeedsUpdateConstraints];

[UIView animateWithDuration:animationDuration animations:^{
   [self.view layoutIfNeeded];
}];

Remember this pattern because this should be the correct way to update constraint-based layouts (according to WWDC). You can also add or remove NSLayoutConstraints as long as you call setNeedsUpdateConstraints after.

John Estropia
  • 17,460
  • 4
  • 46
  • 50
  • That's perfect thank you. So I am trying to figure out the workflow here . I set the constraint first , tell the view that the constraint has changed and needs updating . Then within the animation block I update the view. It's just strange for me as to why I cannot place the keyboard height command inline within the block. Is this because of a view hierarchy ? Thank you so much for your help I will try to find some more WWDC videos on this topic! – Marty W Oct 17 '12 at 01:14
  • 2
    Technically, you can set the keyboard height inside the animation block. The important part is to call `setNeedsUpdateConstraints` BEFORE `layoutIfNeeded`. It's my personal habit to, as much as possible, only include relevant methods inside blocks (In this case, only call things you want to animate inside an animation block). – John Estropia Oct 17 '12 at 01:23
  • Thank you so much you have been more than helpful ! – Marty W Oct 17 '12 at 02:28
  • Actually, the views still jump for me with this method...The layout engine will lay it out with the new height immediately, so when does it have a chance to animate? – borrrden Oct 17 '12 at 02:58
  • `setNeedsUpdateConstraints` doesn't trigger layouts immediately. In this snippet the layout happens inside the animation block, which is what we want. Now if you're getting jumps then there's probably somewhere else that is triggering the layout (animations only "jump" when there are two animations happening on a property; the second animation cancels the first one) – John Estropia Oct 17 '12 at 03:08
  • Maybe your `keyboardWillShow:` is getting triggered while your view is still appearing? In that case just call `setNeedsUpdateConstraints` then dont trigger another layout, let the constraint change animate together with the appearance animation. – John Estropia Oct 17 '12 at 03:12
  • borrrden, I'll continue this in your other question. – John Estropia Oct 17 '12 at 03:16
  • @erurainon I have a question. Why minus height to constraint? – Jun Oct 31 '13 at 12:57
  • @Rixian I just used the OP's setup. He might have setup another constraint to be dependent on `self.keyboardHeight` to offset the main view's height. You should adjust accordingly to how your constraints are organized. – John Estropia Nov 01 '13 at 01:05
15

If you're using UITableViewController, keyboard size should be automatically accommodated by iOS to adjust the contentInsets. But if your tableView is inside a UIViewController, you probably wanted to use this:

KeyboardLayoutConstraint in the Spring framework. Simplest solution I've found so far. KeyboardLayoutConstraint

James Tang
  • 995
  • 1
  • 10
  • 17
8

Try the next code. In this case table view lays out at the bottom edge of the screen.

- (void)keyboardWillShow:(NSNotification *)notification { // UIKeyboardWillShowNotification

    NSDictionary *info = [notification userInfo];
    NSValue *keyboardFrameValue = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    CGRect keyboardFrame = [keyboardFrameValue CGRectValue];

    BOOL isPortrait = UIDeviceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation);
    CGFloat keyboardHeight = isPortrait ? keyboardFrame.size.height : keyboardFrame.size.width;

    // constrBottom is a constraint defining distance between bottom edge of tableView and bottom edge of its superview
    constrBottom.constant = keyboardHeight; 
    // or constrBottom.constant = -keyboardHeight - in case if you create constrBottom in code (NSLayoutConstraint constraintWithItem:...:toItem:...) and set views in inverted order

    [UIView animateWithDuration:animationDuration animations:^{
        [tableView layoutIfNeeded];
    }];
}


- (void)keyboardWillHide:(NSNotification *)notification { // UIKeyboardWillHideNotification

    NSDictionary *info = [notification userInfo];
    NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];

    constrBottom.constant = 0;
    [UIView animateWithDuration:animationDuration animations:^{
        [tableView layoutIfNeeded];
    }];
}
Grigori A.
  • 2,628
  • 1
  • 21
  • 19
  • The animation is not perfect. The view does not move along with the keyboard. There is a delay when the keyboard is shown, it appears after about a second. – Gabriel Diaconescu May 09 '14 at 16:11
  • @GabrielDiaconescu I haven't experienced any lag – Grigori A. May 12 '14 at 14:57
  • This has worked fine for me with iOS7. However with iOS8 on iPad in landscape mode, the keyboardFrame width is swapped with the keyboardFrame height. I'm trying to find a pretty fix for iOS8. – neoneye Sep 25 '14 at 19:07
  • solution for iPhone+iPad on iOS8: CGRect keyboardFrame = [keyboardFrameValue CGRectValue]; CGFloat keyboardHeight = keyboardFrame.size.height; – neoneye Sep 25 '14 at 20:02
2

The approach I took is to add a view which follows the size of the keyboard. Add it below your tableview, or text input or whatever and it will push things up when the keyboard appears.

This is how I set up the view hierarchy:

NSDictionary *views = @{@"chats": self.chatsListView, @"reply": self.replyBarView, @"fakeKeyboard":self.fakeKeyboardView};
[self.view addVisualConstraints:@"V:|-30-[chats][reply][fakeKeyboard]|" views:views];

And then the key bits of the keyboard-size-following view look like this:

- (void)keyboardWillShow:(NSNotification *)notification
{
    // Save the height of keyboard and animation duration
    NSDictionary *userInfo = [notification userInfo];
    CGRect keyboardRect = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    self.desiredHeight = CGRectGetHeight(keyboardRect);
    self.duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];

    [self animateSizeChange];
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    self.desiredHeight = 0.0f;

    [self animateSizeChange];
}

- (CGSize)intrinsicContentSize
{
    return CGSizeMake(UIViewNoIntrinsicMetric, self.desiredHeight);
}

- (void)animateSizeChange
{
    [self invalidateIntrinsicContentSize];

    // Animate transition
    [UIView animateWithDuration:self.duration animations:^{
        [self.superview layoutIfNeeded];
    }];
}

The nice thing about letting this particular view handle its resizing is that you can let the view controller ignore it, and you can also re-use this view any place in your app you want to shift everything up.

The full file is here: https://gist.github.com/shepting/6025439

Steven Hepting
  • 12,394
  • 8
  • 40
  • 50