Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
542 views
in Technique[技术] by (71.8m points)

ios - How to properly animate UIScrollView contentOffset

I have UIScrollView subclass. Its content is reusable - about 4 or 5 views are used to display hundreds of elements (while scrolling hidden objects reused and jumps to another position when its needed to see them)

What i need: ability to automatically scroll my scroll view to any position. For example my scroll view displays 4th, 5th and 6th element and when I tap some button it needs to scroll to 30th element. In other words I need standard behaviour of UIScrollView.

This works fine:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

but I need some customisation. For example, change animation duration, add some code to perform on end of animation.

Obvious decision:

[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
    [self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
    //some code
}];

but I have some actions connected to scroll event, and so now all of them are in animation block and it causes all subview's frames to animate too (thanks to few reusable elements all of them animates not how i want)

The question is: How can I make custom animation (in fact I need custom duration, actions on end and BeginFromCurrentState option) for content offset WITHOUT animating all the code, connected to scrollViewDidScroll event?

UPD: Thanks to Andrew's answer(first part) I solved issue with animation inside scrollViewDidScroll:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [UIView performWithoutAnimation:^{
        [self refreshTiles];
    }];
}

But scrollViewDidScroll must (for my purposes) executes every frame of animation like it was in case of

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

However, now it executes only once at start of animation.

How can I solve this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...