UIScrollView暂停NSTimer,直到滚动完成


Answers:


201

一个易于实现的解决方案是:

NSTimer *timer = [NSTimer timerWithTimeInterval:... 
                                         target:...
                                       selector:....
                                       userInfo:...
                                        repeats:...];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

2
UITrackingRunLoopMode是一种模式-及其公共模式-您可以用来创建不同的计时器行为。
汤姆·安德森

喜欢它,也可以像GLKViewController一样发挥魅力。只需在出现UIScrollView时将controller.paused设置为YES,然后按照说明启动自己的计时器即可。取消滚动视图时将其反转,仅此而已!我的更新/渲染循环不是很昂贵,所以可能会有所帮助。
Jeroen Bouma 2013年

3
太棒了!使计时器无效时是否必须删除计时器?谢谢
Jacopo Penzo'9

这适用于滚动,但在应用程序进入后台时停止计时器。有两种解决方案吗?
Pulkit Sharma

23

对于使用Swift 3的任何人

timer = Timer.scheduledTimer(timeInterval: 0.1,
                            target: self,
                            selector: aSelector,
                            userInfo: nil,
                            repeats: true)


RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)

7
最好将其timer = Timer(timeInterval: 0.1, target: self, selector: aSelector, userInfo: nil, repeats: true)作为第一个命令而不是进行调用Timer.scheduleTimer(),因为scheduleTimer()它将计时器添加到运行循环中,而下一个调用是另一个添加至相同运行循环但具有不同模式的调用。不要做两次相同的工作。
Accid Bright

8

是的,Paul是对的,这是一个运行循环问题。具体来说,您需要使用NSRunLoop方法:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

7

这是快速版本。

timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: aSelector, userInfo: nil, repeats: true)
            NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

6

如果要在滚动时触发计时器,则必须运行另一个线程和另一个运行循环。由于计时器是作为事件循环的一部分进行处理的,因此,如果您正在忙于处理滚动视图,则永远都不会使用计时器。尽管在其他线程上运行计时器的性能/电池损失可能不值得处理这种情况。


11
它不需要其他线程,请参见Kashif的最新答案。我尝试了它,不需要额外的线程。
progrmr

3
向麻雀射击大炮。代替线程,只需将计时器安排在正确的运行循环模式下即可。
uliwitness 2011年

2
我认为下面的Kashif答案是最好的答案,因为它只需要您添加1行代码即可解决此问题。
达明·墨菲。

3

对于使用Swift 4的任何人:

    timer = Timer(timeInterval: 1, target: self, selector: #selector(timerUpdated), userInfo: nil, repeats: true)
    RunLoop.main.add(timer, forMode: .common)

1

tl; dr runloop正在滚动,因此它无法处理更多事件-除非您手动设置计时器,以便它在runloop处理触摸事件时也能发生。或尝试其他解决方案并使用GCD


必须阅读任何iOS开发人员。许多事情最终都是通过RunLoop执行的。

源自Apple的文档

什么是运行循环?

运行循环非常像其名称听起来。这是您的线程进入并用于运行事件处理程序以响应传入事件的循环

如何中断事件的发送?

由于在运行运行循环时会传递计时器和其他定期事件,因此规避该循环会破坏这些事件的传递。每当您通过进入循环并重复从应用程序请求事件来实现鼠标跟踪例程时,就会出现此行为的典型示例。因为您的代码是直接捕获事件,而不是让应用程序正常分配事件,所以活动计时器将无法触发,直到您的鼠标跟踪例程退出并将控制权返回给应用程序之后。

如果在执行过程中运行循环时触发计时器,会发生什么情况?

这种情况发生了很多次,而我们却没有注意到。我的意思是我们将计时器设置为在10:10:10:00触发,但是runloop正在执行一个事件,该事件持续到10:10:10:05,因此计时器被触发10:10:10:06

同样,如果运行循环在执行处理程序例程的中间触发计时器,则计时器将等到下一次通过运行循环调用其处理例程。如果运行循环根本没有运行,则计时器永远不会触发。

在我的计时器启动时,是否会一直滚动或使运行循环保持忙碌状态?

您可以将计时器配置为仅一次或重复生成事件。重复计时器会根据计划的触发时间(而不是实际的触发时间)自动重新计划自身。例如,如果计划将计时器在特定时间触发,然后每5秒触发一次,则即使实际触发时间被延迟,计划的触发时间也将始终落在原始的5秒时间间隔上。如果触发时间延迟得太多,以至于错过了一个或多个计划的触发时间,则计时器将在错过的时间段内仅触发一次。在错过了一段时间后触发后,计时器将重新安排为下一个计划的触发时间。

如何更改RunLoops的模式?

你不能 操作系统只是为您自己进行更改。例如,当用户点击时,模式切换为eventTracking。用户点击完成后,模式返回default。如果您希望某些东西在特定模式下运行,则由您确定是否会发生。


解:

当用户滚动时,运行循环模式变为tracking。RunLoop设计用于变速。一旦将模式设置为eventTracking,则它将优先考虑触摸事件(请记住我们的CPU内核数量有限)。这是OS设计师的体系结构设计

默认情况下,计时器不在该tracking模式下安排。他们计划在:

创建一个计时器,并在默认模式下将其安排在当前运行循环上 。

scheduledTimer下做到这一点:

RunLoop.main.add(timer, forMode: .default)

如果要使计时器在滚动时工作,则必须执行以下任一操作:

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self,
 selector: #selector(fireTimer), userInfo: nil, repeats: true) // sets it on `.default` mode

RunLoop.main.add(timer, forMode: .tracking) // AND Do this

或者只是做:

RunLoop.main.add(timer, forMode: .common)

最终执行上述操作之一意味着您的线程不会被触摸事件阻塞。等效于:

RunLoop.main.add(timer, forMode: .default)
RunLoop.main.add(timer, forMode: .eventTracking)
RunLoop.main.add(timer, forMode: .modal) // This is more of a macOS thing for when you have a modal panel showing.

替代解决方案:

您可以考虑将GCD用作计时器,这将帮助您“屏蔽”代码免受运行循环管理问题的影响。

对于非重复使用:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    // your code here
}

对于重复计时器,请使用:

查看如何使用DispatchSourceTimer


从与Daniel Jalkut的讨论中深入探讨:

问题:如何在RunLoop外部执行GCD(后台线程),例如后台线程上的asyncAfter?我的理解是,一切都将在RunLoop中执行

不一定-每个线程最多具有一个运行循环,但如果没有理由协调线程的执行“所有权”,则每个线程可以具有零。

线程是操作系统级别的功能,使您的进程能够在多个并行执行上下文中拆分其功能。运行循环是框架级别的功能,允许您进一步拆分单个线程,以便可以由多个代码路径有效地共享它。

通常,如果您调度要在线程上运行的内容,则除非有[NSRunLoop currentRunLoop]隐式创建线程的调用,否则它可能不会有运行循环。

简而言之,模式基本上是输入和计时器的过滤机制

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.