How to change UIScrollView contentOffset animation speed?
Solution 1
Unfortunately there is no clean and easy way to do that. Here is a slightly brutal, but working approach:
1) Add CADisplayLink
as a property:
@property (nonatomic, strong) CADisplayLink *displayLink;
2) Animate content offset:
CGFloat duration = 2.0;
// Create CADisplay link and add it to the run loop
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_displayLinkTick)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[UIView animateWithDuration:duration animations:^{
self.scrollView.contentOffset = newContentOffset;
} completion:^(BOOL finished) {
// Cleanup the display link
[self.displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.displayLink = nil;
}];
3) Finally observe the changes on presentationLayer
like:
- (void)_displayLinkTick {
CALayer *presentationLayer = (CALayer *)self.scrollView.layer.presentationLayer;
CGPoint contentOffset = presentationLayer.bounds.origin;
[self _handleContentOffsetChangeWithOffset:contentOffset];
}
- (void)_handleContentOffsetChangeWithOffset:(CGPoint)offset {
// handle offset change
}
Solution 2
To get periodic information about the scroll state, you could run the animation in steps. The delegate will get called once (scrollViewDidScroll:) for each step
- (void)scrollTo:(CGPoint)offset completion:(void (^)(BOOL))completion {
// this presumes an outlet called scrollView. You could generalize by passing
// the scroll view, or even more generally, place this in a UIScrollView category
CGPoint contentOffset = self.scrollView.contentOffset;
// scrollViewDidScroll delegate will get called 'steps' times
NSInteger steps = 10;
CGPoint offsetStep = CGPointMake((offset.x-contentOffset.x)/steps, (offset.y-contentOffset.y)/steps);
NSMutableArray *offsets = [NSMutableArray array];
for (int i=0; i<steps; i++) {
CGFloat stepX = offsetStep.x * (i+1);
CGFloat stepY = offsetStep.y * (i+1);
NSValue *nextStep = [NSValue valueWithCGPoint:CGPointMake(contentOffset.x+stepX, contentOffset.y+stepY)];
[offsets addObject:nextStep];
}
[self scrollBySteps:offsets completion:completion];
}
// run several scroll animations back-to-back
- (void)scrollBySteps:(NSMutableArray *)offsets completion:(void (^)(BOOL))completion {
if (!offsets.count) return completion(YES);
CGPoint offset = [[offsets objectAtIndex:0] CGPointValue];
[offsets removeObjectAtIndex:0];
// total animation time == steps * duration. naturally, you can fool with both
// constants. to keep the rate constant, set duration == steps * k, where k
// is some constant time per step
[UIView animateWithDuration:0.1 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
self.scrollView.contentOffset = offset;
} completion:^(BOOL finished) {
[self scrollBySteps:offsets completion:completion];
}];
}
Call it like this...
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height);
[self scrollTo:bottomOffset completion:^(BOOL finished) {}];
// BONUS completion handler! you can omit if you don't need it
Solution 3
Below code solved my issue
CGPoint leftOffset = CGPointMake(0, 0);
[UIView animateWithDuration:.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
[self.topBarScrollView setContentOffset:leftOffset animated:NO];
} completion:nil];
openfrog
Thanks a lot to Pascal MARTIN, Gumbo and Quassnoi for their great help. Also many thanks to everyone else answering my questions.
Updated on June 11, 2022Comments
-
openfrog almost 2 years
Is there a way to change contentOffset animation speed without creating your own animation that sets the contentOffset?
The reason why I can't create my own animation for contentOffset change is that this will not call -scrollViewDidScroll: in regular intervals during animation.
-
jede over 10 yearsDoesn't work. Tried setting a different duration, and its still about 500 ms.
-
Nikolay Spassov over 9 yearsI think step 3 is the important one to have in mind: the current state of the UI is given by the presentationLayer.
-
Arjun Mehta over 9 years@kamilkocemba This is an amazing tip in general! It's actually a really efficient way to use a single animation to drive the positions of other elements. Thanks!
-
Asfanur about 9 yearsIts works. But any button on UIScrollView does not receive touch event.
-
buildsucceeded over 7 yearsBut… what do you do in
_handleContentOffsetChangeWithOffset
? -
Iulian Onofrei almost 7 years@buildsucceeded, The same code you have in your
scrollViewDidScroll:
method. -
Iulian Onofrei almost 7 yearsInteresting use of the
presentationLayer
. -
arsenius about 6 yearsThis works great with
UIViewControllerAnimatedTransitioning
to animate a view controller that's using autolayout (the two don't play nicely together). Animate a dummy UIView's frame, and track the presentation layer to update the view controller's frame. -
Kirby Todd almost 6 yearscodegists.com/snippet/swift/moscrollviewswiftswift_tkirby_swift Swift class with func setContentOffset(contentOffset: CGPoint, with timingFunction: CAMediaTimingFunction, duration: CFTimeInterval) I added it as an answer but some busybody mod deleted it.
-
battlmonstr about 2 yearsNot working for me. Setting 0.1 doesn't make it faster.