当UIScrollView事件发生时,NSTimer未触发。

18

我在UIScrollView中放置了一个UIImageView,这个UIImageView基本上保存着一张非常大的地图,并用“箭头”指向导航方向,在预定义的路径上创建动画。

但是,每当发生uiscrollevents事件时,我认为MainLoop会冻结,NSTimer不会被触发,动画也会停止。

是否存在任何现有的属性可以解决此问题,例如UIScrollView、CAKeyFrameAnimation或NSTimer?

//viewDidLoad
   self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(drawLines:) userInfo:nil repeats:YES];

- (void)drawLines:(NSTimer *)timer {

   CALayer *arrow = [CALayer layer];
   arrow.bounds = CGRectMake(0, 0, 5, 5);
   arrow.position = CGPointMake(line.x1, line.y1);
   arrow.contents = (id)([UIImage imageNamed:@"arrow.png"].CGImage);

   [self.contentView.layer addSublayer:arrow];

   CAKeyframeAnimation* animation = [CAKeyframeAnimation animation];
   animation.path = path;
   animation.duration = 1.0;
   animation.rotationMode = kCAAnimationRotateAuto; // object auto rotates to follow the path
   animation.repeatCount = 1;
   animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
   animation.fillMode = kCAFillModeForwards;

   [arrow addAnimation:animation forKey:@"position"];
}

可能是UIScrollView pauses NSTimer while scrolling的重复问题。 - rob mayoff
2个回答

63

iOS应用程序在NSRunLoop上运行。每个NSRunLoop都有不同的执行模式,用于不同的任务。例如,默认的NSTimer安排在NSDefaultRunMode下在NSRunLoop中运行。然而,这意味着特定的UI事件(如滚动)将打断计时器,并将其放入队列中,在事件停止更新后尽快运行它。在您的情况下,为了使计时器不被中断,您需要将其安排在另一种模式下,即NSRunLoopCommonModes,如下所示:

  self.myTimer =  [NSTimer scheduledTimerWithTimeInterval:280
                                                                 target:self
                                                               selector:@selector(doStuff)
                                                               userInfo:nil
                                                                repeats:NO];
  [[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSRunLoopCommonModes]; 

这种模式可以使你的计时器不会被滚动打扰。你可以在此处找到更多相关信息:https://developer.apple.com/documentation/foundation/nsrunloop。在底部,你会看到可以选择的模式定义。传说中,你也可以编写自己的自定义模式,但很遗憾,只有少数人活下来并讲述了这个故事。


4
嘿@GregPrice。感谢您即使在3年后还能提供解释。然而,文档说scheduledTimerWithTimeInterval:...创建一个计时器并将其安排在当前运行循环的默认模式中。那么为什么您要在默认RunLoop中添加两次,而不是在NSRunLoopCommonModes模式下只添加一次? 嘿@GregPrice。感谢你即使是三年后仍然解释。然而,这份文档指出scheduledTimerWithTimeInterval:...会创建一个计时器,并在默认模式下将其调度到当前运行循环中。所以为什么你需要在默认RunLoop中添加两次,而不是在NSRunLoopCommonModes模式下只添加一次? - Martin
1
UIScrollViewUITrackingRunLoopMode 下运行循环,这与 NSDefaultRunLoopMode 不同。如果您想要在其他模式下运行它,仅安排默认模式的计时器是不够的。 - rob mayoff
5
针对 Swift 3:RunLoop.current.add(self.myTimer, forMode: .commonModes) 的翻译如下:使用 Swift 3:`RunLoop.current.add(self.myTimer, forMode: .commonModes)` (注意:这是一个程序代码,如果需要更多的上下文或说明,请提供更多信息) - Steve Cotner

1

还有一件事 (c)

  1. 使用timerWithTimeInterval方法,以避免将定时器添加到DefaultMode运行循环中
  2. 使用mainRunLoop

所以:

self.myTimer = [NSTimer timerWithTimeInterval:280 target:self selector:@selector(doStuff) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.myTimer forMode:NSRunLoopCommonModes];


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接