Trying to do something similar to the Messages.app's behavior, I have a UIScrollView
and beneath it a text field, and trying to animate it so that when the keyboard appears everything is moved up above the keyboard using a constraint that moves the field up (and the UIScrollView
's height changes as well due to autolayout) and also setting the contentOffset
to scroll to the bottom at the same time.
The code accomplishes the wanted end-result, but during the animation right when the keyboard animation begins the scroll view becomes blank and then the content scrolls up from the bottom, instead of scrolling from the position it was in when the animation started.
The animation is this:
- (void)updateKeyboardConstraint:(CGFloat)height animationDuration:(NSTimeInterval)duration {
self.keyboardHeight.constant = -height;
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self.view layoutIfNeeded];
self.collectionView.contentOffset =
CGPointMake(0, self.collectionView.contentSize.height - self.collectionView.bounds.size.height);
} completion:nil];
}
A video of the problem is available here.
Thanks!
It might be a bug in UIKit. It happens when there's a simultaneous change of size
and contentOffset
of UIScrollView
. It'd be interesting to test if this behavior also happens without Auto Layout.
I've found two workarounds to this problem.
As it can be seen in the Messages app, UIScrollView
's height doesn't change when a keyboard is shown - messages are visible under the keyboard. You can do it the same way. Remove constraint between UICollectionView
and the view that contains UITextField
and UIButton
(I'll call it messageComposeView
). Then add constraint between UICollectionView
and Bottom Layout Guide
. Keep the constraint between messageComposeView
and the Bottom Layout Guide
. Then use contentInset
to keep the last element of the UICollectionView
visually above the keyboard. I did it the following way:
- (void)updateKeyboardConstraint:(CGFloat)height animationDuration:(NSTimeInterval)duration {
self.bottomSpaceConstraint.constant = height;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
CGPoint bottomOffset = CGPointMake(0, self.collectionView.contentSize.height - (self.collectionView.bounds.size.height - height));
[self.collectionView setContentOffset:bottomOffset animated:YES];
[self.collectionView setContentInset:UIEdgeInsetsMake(0, 0, height, 0)];
[self.view layoutIfNeeded];
} completion:nil];
}
Here self.bottomSpaceConstraint
is a constraint between messageComposeView
and Bottom Layout Guide
. Here's the video showing how it works.
UPDATE 1: Here's my project's source on GitHub. This project is a little simplified. I should've taken into consideration options passed in the notification in - (void)keyboardWillShow:(NSNotification *)notif
.
Not an exact solution, but scrolling works fine if you move it to the completion block:
} completion:^(BOOL finished) { [self.collectionView setContentOffset:CGPointMake(0, self.collectionView.contentSize.height - self.collectionView.bounds.size.height) animated:YES]; }];
It takes 0.25s for the keyboard to show, so the difference between the beginnings of the animations might be noticeable. Animations can also be done in the reversed order.
UPDATE 2: I've also noticed that OP's code works fine with this change:
CGPoint bottomOffset = CGPointMake(0, self.collectionView.contentSize.height - (self.collectionView.bounds.size.height - height));
but only when contentSize
's height
is less than some fixed value (in my case around 800
, but my layout may be a little different).
In the end I think that the approach I presented in Using contentInset (the Messages approach)
is better than resizing UICollectionView
. When using contentInset
we also get the visibility of the elements under the keyboard. It certainly fits the iOS 7 style better.
I had a similar issue -- when animating the frame and offset the contents would "jump" right before animating into position -- and the ENTIRE solution was just adding UIViewAnimationOptionBeginFromCurrentState
to the animation options. Voila!
Try to remove the [self.view layoutIfNeeded]
line and see if the problem persists or if other problems appear and if so if they look related in any way.
Also, it's always a good idea to reset the position of your views right before the animation. So try to set the normal offset just before the animation line (and maybe even call the layoutIfNeeded
method there) Sort of placing everything in order right before you start the animation.
Does the setNeedsUpdateConstraints
really needed? isn't auto layout doing it automatically?
If not- I would suggest you doing all the resizing animation alone without using auto layout. it looks like the problem is when you try doing both animations together (but it's not on the same time)
I'm not sure it's exactly the behaviour you want, but maybe it can give you a nudge in the right direction : Github project
What I did was to set up two constraints, one for the text field (to the bottom guide) and the other for the scroll view (to the textfield).
Then when calling the animation I animate the "center" property of both elements, not the contentOffset, and I treat the animation value and the constraint value separately.
Final result is here:
Youtube video
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With