【问题标题】:Change the speed of setContentOffset:animated:?改变 setContentOffset:animated: 的速度?
【发布时间】:2010-12-10 01:06:17
【问题描述】:

有没有办法在使用 setContentOffset:animated: 滚动 UITableView 时改变动画的速度?我想将它滚动到顶部,但速度很慢。当我尝试以下操作时,它会导致底部的几个单元格在动画开始之前消失(特别是在滚动完成时不可见的单元格):

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:3.0];
[self.tableView setContentOffset:CGPointMake(0, 0)];
[UIView commitAnimations];

还有其他方法可以解决这个问题吗?有一个私有方法_setContentOffsetAnimationDuration 有效,但我不想被应用商店拒绝。

【问题讨论】:

  • 请看下面我的回答。您实际上可以通过合法方式访问 _setContentOffsetAnimationDuration。感谢您注意到它存在于您的原始问题中。 ;-)

标签: iphone objective-c uitableview animation


【解决方案1】:
[UIView animateWithDuration:2.0 animations:^{
    scrollView.contentOffset = CGPointMake(x, y);
}];

有效。

【讨论】:

  • 好吧,如果您的滚动视图是 uitableview,它就不起作用。单元格的创建和重用失败。
  • 在我的 UIscrollview 上运行流畅且非常好
  • 嗯,它有效,但不是在问题的上下文中。我的细胞也在消失!
  • 这是不正确的。如果由于单元格重复使用不当而滚动超过一页,则会导致不良行为。
  • @DancOfDeth 是的。您必须自己进行滚动。将 CADisplayLink 设置为每 1/60 秒触发一次并更新 contentOffset。我还推荐 AHFunction 以获得漂亮的动画曲线。
【解决方案2】:

直接设置内容偏移对我不起作用。但是,将 setContentOffset(offset, animated: false) 包裹在一个动画块中就可以了。

UIView.animate(withDuration: 0.5, animations: {
                self.tableView.setContentOffset(
               CGPoint(x: 0, y: yOffset), animated: false)
            })

【讨论】:

  • 谢谢,这很好用。在此问题的其他答案中,它没有提出 cmets 中描述的任何问题。
  • 在 Swift 4.0 中工作
  • 我有工作 scrollViewDidScroll 方法与其他视图取决于滚动偏移量。并且只使用动画,它只触发一次,我没有得到依赖视图的平滑动画。它只是立即跳到最终位置(((
  • 它和这个答案stackoverflow.com/a/9106903/1050261有同样的问题 - 正确重用单元格失败,它们可能会在动画开始之前消失。
【解决方案3】:

我接受了 nacho4d 的回答并实现了代码,所以我认为这对其他人来这个问题查看工作代码会有所帮助:

我在我的类中添加了成员​​变量:

CGPoint startOffset;
CGPoint destinationOffset;
NSDate *startTime;

NSTimer *timer;

和属性:

@property (nonatomic, retain) NSDate *startTime;
@property (nonatomic, retain) NSTimer *timer;

还有一个定时器回调:

- (void) animateScroll:(NSTimer *)timerParam
{
    const NSTimeInterval duration = 0.2;

    NSTimeInterval timeRunning = -[startTime timeIntervalSinceNow];

    if (timeRunning >= duration)
    {
        [self setContentOffset:destinationOffset animated:NO];
        [timer invalidate];
        timer = nil;
        return;
    }
    CGPoint offset = [self contentOffset];

    offset.x = startOffset.x +
        (destinationOffset.x - startOffset.x) * timeRunning / duration;

    [self setContentOffset:offset animated:NO];
}

然后:

- (void) doAnimatedScrollTo:(CGPoint)offset
{
    self.startTime = [NSDate date];
    startOffset = self.contentOffset;
    destinationOffset = offset;

    if (!timer)
    {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01
                                                      target:self
                                                    selector:@selector(animateScroll:)
                                                    userInfo:nil
                                                     repeats:YES];
    }
}

您还需要在 dealloc 方法中进行计时器清理。由于计时器将保留对目标(self)的引用,并且 self 具有对计时器的引用,因此在 viewWillDisappear 中取消/销毁计时器的一些清理代码也可能是个好主意。

欢迎任何关于上述内容的 cmets 或改进建议,但它对我来说效果很好,并解决了我在使用 setContentOffset:animated: 时遇到的其他问题。

【讨论】:

  • 此代码需要根据原始发布者的要求进行小修改。他想要一个缓慢的上下滚动。所以所有这些 .x 值都应该是 .y。这让它对我有用。
  • 啊,是的,很好,谢谢 - 我实际上是在分页 UIScrollView 中使用上面的代码,实际上并没有注意到问题是关于 UITableView :-)
  • 不要将 NSTimer 用于动画,使用 CADisplayLink。例如:bigspaceship.com/ios-animation-intervals
  • 对于CADisplayLink,可以参考,stackoverflow回答链接stackoverflow.com/a/45641913/2641380
【解决方案4】:

没有直接的方法可以做到这一点,也没有你写的方式。我能做到这一点的唯一方法是自己制作动作/动画。

例如每 1/10 秒移动 1px 应该模拟一个非常慢的滚动动画。 (因为它是一个线性动画数学很容易!)

如果您想获得更逼真或花哨的效果并模拟易进易关效果,那么您需要一些数学来计算贝塞尔路径,以便您可以知道每 1/10 秒的准确位置,例如

至少第一种方法不应该那么困难。 只需使用或-performSelector:withObject:afterDelay or NSTimerswith

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

希望对你有帮助

【讨论】:

  • 是的,但是如果通过设置 contentOffset 手动滚动,您将看不到滚动指示器。
【解决方案5】:

我很好奇您是否找到了解决问题的方法。我的第一个想法是使用 animateWithDuration:animations: 调用并设置 contentOffset:

[UIView animateWithDuration:2.0 animations:^{
    scrollView.contentOffset = CGPointMake(x, y);
}];

副作用

虽然这适用于简单的示例,但它也有非常不希望的副作用。与 setContentOffset:animated: 不同,您在委托方法中所做的所有事情也会被动画化,就像 scrollViewDidScroll: 委托方法一样。

我正在滚动浏览带有可重复使用磁贴的平铺滚动视图。这在scrollViewDidScroll: 中得到检查。当它们确实被重用时,它们会在滚动视图中获得一个新位置,但这会被动画化,因此在整个视图中都有动画块。看起来很酷,但完全没用。另一个不需要的副作用是,可能对图块进行命中测试,并且我的滚动视图的边界会立即变得无用,因为一旦动画块执行,contentOffset 就已经处于新位置。这使得内容在它们仍然可见时出现和消失,至于它们过去在滚动视图边界之外的位置被切换。

setContentOffset:animated: 并非如此。看起来 UIScrollView 内部没有使用相同的技术。

是否有人对更改 UIScrollView setContentOffset:animated: 执行的速度/持续时间有其他建议?

【讨论】:

  • nacho4d 的回答建议使用 NSTimer 手动制作“动画”对我来说效果很好,你试过了吗?
  • 好吧,如果您的滚动视图是 uitableview,它就不起作用。单元格的创建和重用失败。
【解决方案6】:

UIView 计算最终视图,然后对其进行动画处理。这就是为什么动画结束时不可见的单元格在开始时也不可见的原因。为了防止这种需要在动画块中添加 layoutIfNeeded:

[UIView animateWithDuration:2.0 animations:^{
    [self.tableView setContentOffset:CGPointMake(0, 0)];
    [self.tableView layoutIfNeeded]
}];

Swift 版本:

UIView.animate(withDuration: 2) {
    self.tableView.contentOffset.y = 10
    self.tableView.layoutIfNeeded()
}

【讨论】:

  • 添加 layoutIfNeeded 似乎解决了我在 iOS 14 上的问题。谢谢!
【解决方案7】:

https://github.com/dominikhofmann/PRTween

子类 UITableview

#import "PRTween.h"


@interface JPTableView : UITableView{
  PRTweenOperation *activeTweenOperation;
}



- (void) doAnimatedScrollTo:(CGPoint)destinationOffset
{
    CGPoint offset = [self contentOffset];

    activeTweenOperation = [PRTweenCGPointLerp lerp:self property:@"contentOffset" from:offset to:destinationOffset duration:1.5];


}

【讨论】:

  • 完美!正是我需要的!
【解决方案8】:

您可以按如下方式设置持续时间:

scrollView.setValue(5.0, forKeyPath: "contentOffsetAnimationDuration") scrollView.setContentOffset(CGPoint(x: 100, y: 0), animated: true)

这也将允许您获取所有常规委托回调。

【讨论】:

  • 这是一个私有 API,可能会导致 App Store 被拒绝
  • 只是为了澄清新手, setValue(_: forKey:) 不是私有 API。 contentOffsetAnimationDuration 是否应该是“未知的”并因此是私有的,还有待商榷。希望不会,因为 A)这是在规则范围内执行此操作的最干净的方法,并且保持回调不变;和 B) 我即将把它提交给商店。如果炸弹回来了,我会通知我们的。
  • “contentOffsetAnimationDuration”ivar 未公开,因此是私有的。没有什么好争论的。问题是苹果是否会在他们的自动化私有 API 检查系统中发现它。但更重要的是,这不是一种非常安全的做事方式。如果 Apple 更改了该属性的名称(他们完全可以这样做,因为它是私有属性),那么您的应用程序将在该行崩溃。
  • @eGanges 提交时发生了什么?
  • 对不起,这个增强(滚动文本视图)是一个子集的功能集从未前滚到生产应用程序,所以我无法提供更多关于它的好坏的数据苹果会收到。 :-(
【解决方案9】:

如果您要做的只是滚动滚动视图,我认为您应该使用滚动矩形来显示。我刚刚试用了这段代码

 [UIView animateWithDuration:.7
                      delay:0
                    options:UIViewAnimationOptionCurveEaseOut
                 animations:^{  
                     CGRect scrollToFrame = CGRectMake(0, slide.frame.origin.y, slide.frame.size.width, slide.frame.size.height + kPaddingFromTop*2);
                     CGRect visibleFrame = CGRectMake(0, scrollView.contentOffset.y,
                                                      scrollView.frame.size.width, scrollView.frame.size.height);
                     if(!CGRectContainsRect(visibleFrame, slide.frame))
                         [self.scrollView scrollRectToVisible:scrollToFrame animated:FALSE];}];

它会将滚动视图滚动到我设置的任何持续时间所需的位置。关键是将动画设置为false。设置为true时,动画速度为方法设置的默认值

【讨论】:

    【解决方案10】:

    对于在滚动 UITableView 或 UICollectionView 时也遇到项目消失问题的人,您可以扩展视图本身,以便我们保留更多可见项目。对于需要滚动很远距离或用户可以取消动画的情况,不建议使用此解决方案。在我目前正在开发的应用程序中,我只需要让视图滚动固定 100 像素。

    NSInteger scrollTo = 100;

    CGRect frame = self.collectionView.frame;
    frame.size.height += scrollTo;
    [self.collectionView setFrame:frame];
    
    [UIView animateWithDuration:0.8 delay:0.0 options:(UIViewAnimationOptionCurveEaseIn) animations:^{
        [self.collectionView setContentOffset:CGPointMake(0, scrollTo)];
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.8 delay:0.0 options:(UIViewAnimationOptionCurveEaseIn) animations:^{
            [self.collectionView setContentOffset:CGPointMake(0, 0)];
    
        } completion:^(BOOL finished) {
            CGRect frame = self.collectionView.frame;
            frame.size.height -= scrollTo;
            [self.collectionView setFrame:frame];
        }];
    }];
    

    【讨论】:

      【解决方案11】:

      我用transitionWithView:duration:options:animations:completion:

              [UIView transitionWithView:scrollView duration:3 options:(UIViewAnimationOptionCurveLinear) animations:^{
              transitionWithView:scrollView.contentOffset = CGPointMake(contentOffsetWidth, 0);
          } completion:nil];
      

      UIViewAnimationOptionCurveLinear 是一个选项,可以让动画均匀地出现。

      虽然我发现在动画持续时间内,委托方法 scrollViewDidScroll 直到动画完成才被调用。

      【讨论】:

        【解决方案12】:

        您可以简单地使用基于块的动画来为滚动视图的速度设置动画。 首先计算要滚动到的偏移点,然后简单地传递该偏移值,如下所示.....

            [UIView animateWithDuration:1.2
                              delay:0.02 
                              options:UIViewAnimationCurveLinear  
                              animations:^{
             [colorPaletteScrollView setContentOffset: offset ];
         }
         completion:^(BOOL finished)
         { NSLog(@"animate");
         } ];
        

        这里 colorPaletteScrollView 是我的自定义滚动视图,偏移量是传递的值。

        这段代码对我来说非常好。

        【讨论】:

        • 好吧,如果您的滚动视图是 uitableview,它就不起作用。单元格的创建和重用失败。
        【解决方案13】:

        您使用 setContentOffset 而不是 scrollRectToVisible:animated: 是否有原因?

        - (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
        

        我建议这样做:

        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:3.0];
        [self.tableView scrollRectToVisible:CGRectMake(0, 0, 320, 0) animated:NO];
        [UIView commitAnimations];
        

        除非那不起作用。我还是觉得你应该试试看。

        【讨论】:

        • 不适用于表格视图或集合视图。不相关的答案
        【解决方案14】:

        其实TK189的回答是部分正确的。

        要实现自定义持续时间动画 contentOffset 更改,通过 UITableView 和 UICollectionView 组件正确重用​​单元格,您只需在动画块内添加 layoutIfNeeded 调用:

        [UIView animateWithDuration:2.0 animations:^{
            tableView.contentOffset = CGPointMake(x, y);
            [tableView layoutIfNeeded];
        }];
        

        【讨论】:

        • 这对我不起作用。集合视图单元格仍然消失。
        • @tbag 我认为问题出在代码的其他地方。如果你给我们一个例子,我们可能会提供帮助。
        【解决方案15】:

        在 Xcode 7.1 - Swift 2.0 上:

        func textFieldShouldEndEditing(textField: UITextField) -> Bool {
        
            dispatch_async(dispatch_get_main_queue()) {
                UIView.animateWithDuration(0, animations: { self.scrollView!.setContentOffset(CGPointZero,animated: true) })
            }
            return true
        }
        

        func textFieldShouldReturn(textField: UITextField) -> Bool {
        
            if(textField.returnKeyType == UIReturnKeyType.Next) {
                password.becomeFirstResponder()
            }
        
            dispatch_async(dispatch_get_main_queue()) {
                UIView.animateWithDuration(0, animations: { self.scrollView!.setContentOffset(CGPointZero,animated: true) })
            }
        
            textField.resignFirstResponder()
            return true
        }
        

        注意:self.scrollView!.setContentOffset(CGPointZero,animated: true) 可以根据需要有不同的位置

        例子:

        let scrollPoint:CGPoint = CGPointMake(0,textField.frame.origin.y/2);
        scrollView!.setContentOffset(scrollPoint, animated: true);
        

        【讨论】:

          【解决方案16】:

          我想在 textfield 开始编辑时更改 tableview 的 contentOffSet。

          Swift 3.0

          func textFieldDidBeginEditing(_ textField: UITextField) {
          
          DispatchQueue.main.async { 
              UIView.animate(withDuration: 0, animations: {
          
               self.sampleTableView.contentOffset = CGPoint(x: 0, y: 0 - (self.sampleTableView.contentInset.top - 200 ))
              }) 
          }    
          }
          

          【讨论】:

            猜你喜欢
            • 2013-03-25
            • 2018-12-09
            • 2013-04-27
            • 1970-01-01
            • 2015-05-27
            • 2015-07-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多