How to Properly Animate Uiscrollview Contentoffset

How to properly animate UIScrollView contentOffset

Did you try the same approach, but with disabled animation in scrollViewDidScroll ?

On iOS 7, you could try wrapping your code in scrollViewDidScroll in

[UIView performWithoutAnimation:^{
//Your code here
}];

on previous iOS versions, you could try:

  [CATransaction begin];
[CATransaction setDisableActions:YES];
//Your code here
[CATransaction commit];

Update:

Unfortunately that's where you hit the tough part of the whole thing. setContentOffset: calls the delegate just once, it's equivalent to setContentOffset:animated:NO, which again calls it just once.

setContentOffset:animated:YES calls the delegate as the animation changes the bounds of the scrollview and you want that, but you don't want the provided animation, so the only way around this that I can come up with is to gradually change the contentOffset of the scrollview, so that the animation system doesn't just jump to the final value, as is the case at the moment.

To do that you can look at keyframe animations, like so for iOS 7:

[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
}];
} completion:^(BOOL finished) {
//Completion Block
}];

This will get you two updates and of course you could use some math and a loop to add up a lot more of these with the appropriate timings.

On previous iOS versions, you'll have to drop to CoreAnimation for keyframe animations, but it's basically the same thing with a bit different syntax.

Method 2:
You can try polling the presentationLayer of the scrollview for any changes with a timer that you start at the beginning of the animation, since unfortunately the presentationLayer's properties aren't KVO observable. Or you can use needsDisplayForKey in a subclass of the layer to get notified when the bounds change, but that'll require some work to set up and it does cause redrawing, which might affect performance.

Method 3:
Would be to dissect exactly what happens to the scrollView when animated is YES try and intercept the animation that gets set on the scrollview and change its parameters, but since this would be the most hacky, breakable due to Apple's changes and trickiest method, I won't go into it.

Smooth move of the contentoffset UIScrollView Swift

You can use UIView.animations

  func goToPoint() {
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(2, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}
}

Swift version

DispatchQueue.main.async {
UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.myScrollView.contentOffset.x = CGFloat(startingPointForView)
}, completion: nil)
}

Swift 4

DispatchQueue.main.async {
UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: {
self.scrollView.contentOffset.x = 200
}, completion: nil)
}

Can I animate the UIScrollView contentOffset property via its layer?

You have to animate the bounds property. In fact, that's what the contentOffset property uses behind the scenes.

Example:

CGRect bounds = scrollView.bounds;

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
animation.duration = 1.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

animation.fromValue = [NSValue valueWithCGRect:bounds];

bounds.origin.x += 200;

animation.toValue = [NSValue valueWithCGRect:bounds];

[scrollView.layer addAnimation:animation forKey:@"bounds"];

scrollView.bounds = bounds;

If you're curious, the way I used to get this information is the following:

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];

[scrollView setContentOffset:CGPointMake(200, 0) animated:NO];

[UIView commitAnimations];

NSLog(@"%@",scrollView);

The NSLog call will output:

<UIScrollView: 0x860ba20; frame = (-65.5 0; 451 367); clipsToBounds = YES; autoresize = W+RM+TM+H; animations = { bounds=<CABasicAnimation: 0xec1e7c0>; }; layer = <CALayer: 0x860bbc0>; contentOffset: {246, 0}>

The animations snippet will list all the active animations, in this case { bounds=<CABasicAnimation: 0xec1e7c0>; }.

Hope this helps.

UIScrollView animation of height and contentOffset jumps content from bottom

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.

Using contentInset (the Messages approach)

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.

Performing changes in a queue

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.

UIScrollView setContentOffset:animated not working in iOS11

try this

dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:.25 animations:^{
[self setContentOffset:CGPointZero animated:YES];
}];
});

Change the speed of setContentOffset:animated:?

There is no a direct way of doing this, nor doing the way you wrote it. The only way I can accomplish this is by making the movement/animation by my own.

For example move 1px every 1/10 second should simulate a very slow scroll animation. (Since its a linear animation maths are very easy!)

If you want to get more realistic or fancy and simulate easy-in easy-off effect then you need some maths to calculate a bezier path so you can know the exact position at every 1/10 second, for example

At least the first approach shouldn't be that difficult.
Just use or -performSelector:withObject:afterDelay or NSTimerswith

-[UIScrollView setContentOffset:(CGPoint*)];`

Hope it helps



Related Topics



Leave a reply



Submit