如何正确设置UIScrollView contentOffset的动画

Llo*_*d18 18 cocoa-touch uiscrollview uiview uiviewanimation ios

我有UIScrollView子类.它的内容是可重用的 - 大约有4或5个视图用于显示数百个元素(同时滚动隐藏的对象重用,并在需要查看它们时跳转到另一个位置)

我需要的是:能够自动滚动我的滚动视图到任何位置.例如,我的滚动视图显示第4,第5和第6个元素,当我点击某个按钮时,它需要滚动到第30个元素.换句话说,我需要UIScrollView的标准行为.

这很好用:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
Run Code Online (Sandbox Code Playgroud)

但我需要一些定制.例如,更改动画持续时间,添加一些代码以在动画结束时执行.

明显的决定:

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

但我有一些动作连接到滚动事件,所以现在它们都在动画块中,它导致所有子视图的帧也动画(感谢几个可重复使用的元素,所有这些都不是我想要的动画)

问题是:我如何制作自定义动画(实际上我需要自定义持续时间,最后操作和BeginFromCurrentState选项)以进行内容偏移而不动画连接到scrollViewDidScroll事件的所有代码?

UPD: 感谢Andrew的回答(第一部分)我解决了内部动画的问题scrollViewDidScroll:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [UIView performWithoutAnimation:^{
        [self refreshTiles];
    }];
}
Run Code Online (Sandbox Code Playgroud)

scrollViewDidScroll必须(为了我的目的)执行每一帧动画,就像它的情况一样

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
Run Code Online (Sandbox Code Playgroud)

但是,现在它只在动画开始时执行一次.

我怎么解决这个问题?

And*_*rew 18

您是否尝试过相同的方法,但是禁用了动画scrollViewDidScroll

在iOS 7上,您可以尝试将代码包装scrollViewDidScroll 进去

[UIView performWithoutAnimation:^{
       //Your code here
    }];
Run Code Online (Sandbox Code Playgroud)

在以前的iOS版本中,您可以尝试:

  [CATransaction begin];
    [CATransaction setDisableActions:YES];
     //Your code here
    [CATransaction commit];
Run Code Online (Sandbox Code Playgroud)

更新:

不幸的是,这就是你在整个事情中遇到困难的部分.setContentOffset:只调用一次委托,它相当于setContentOffset:animated:NO,只调用一次.

setContentOffset:animated:YES当动画改变了scrollview的边界并且你想要它时,调用委托,但你不想要提供的动画,所以我能想出的唯一方法是逐步改变contentOffsetscrollview,这样动画系统不只是跳到最终值,就像目前的情况一样.

要做到这一点,您可以查看关键帧动画,就像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
}];
Run Code Online (Sandbox Code Playgroud)

这将为您提供两个更新,当然您可以使用一些数学和一个循环来添加更多这些与适当的时间.

在之前的iOS版本中,你必须使用关键帧动画来降级到CoreAnimation,但它的语法略有不同.

方法2:您可以尝试使用从动画开始时开始的计时器轮询scrollview的presentationLayer以进行任何更改,因为不幸的是,presentationLayer的属性不是KVO可观察的.或者,您可以needsDisplayForKey在图层的子类中使用,以便在边界更改时收到通知,但这需要一些工作来设置,这确实会导致重绘,这可能会影响性能.

方法3:当动画为YES时,准确剖析scrollView会发生什么,并拦截在scrollview上设置的动画并更改其参数,但由于Apple的更改和最棘手的方法,这将是最hacky,易碎的,我不会进入它.


bca*_*tle 5

一个很好的方法是使用AnimationEngine库。这是一个非常小的库:六个文件,如果您想阻尼弹簧性能,则可以增加三个。

在幕后,它使用a CADisplayLink来每帧运行一次动画块。你得到一个干净的基于块的语法,易于使用,和一堆插值缓解功能为您节省时间。

制作动画contentOffset

startOffset = scrollView.contentOffset;
endOffset = ..

// Constant speed looks good...
const CGFloat kTimelineAnimationSpeed = 300;
CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;

[INTUAnimationEngine animateWithDuration:timelineAnimationDuration
                                   delay:0
                                  easing:INTULinear
                              animations:^(CGFloat progress) {
                                    self.videoTimelineView.contentOffset = 
                                         INTUInterpolateCGPoint(startOffset, endOffset, progress);
                              }
                             completion:^(BOOL finished) {
                                   autoscrollEnabled = YES;
                             }];
Run Code Online (Sandbox Code Playgroud)


ger*_*bil 5

尝试这个:

UIView.animate(withDuration: 0.6, animations: {
        self.view.collectionView.contentOffset = newOffset
        self.view.layoutIfNeeded()
    }, completion: nil)
Run Code Online (Sandbox Code Playgroud)