滑动时的Timer

128 阅读1分钟

如上,在UIScrollView上添加一个UILabel, UILabel的text通过添加一个Timer来每一秒更新一次

func createTimer() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
            self.timerLabel.text = "(self.count + 1)"
            self.count += 1
        })
}

如上为创建Timer代码

从上面gif中可以看出:当滑动屏幕时UILabel的text停止更新了,也就是Timer的回调未被执行,当手离开屏幕停止滑动时,UILabel的text又开始更新了,也就是Timer的回调又被执行了。

这是为什么呢?

当我第一次遇到该类问题时,真是摸不着头脑,算是一个知识盲区

后来上网搜了一下,又学了一下RunLoop,算是明白了,其实这就是一个RunLoop的mode切换问题

必要的知识点:

RunLoop只能同时在一种mode下工作
RunLoop常用的三种mode: 
  kCFRunLoopDefaultMode : App的默认mode,通常主线程是在该mode下运行的
  UITrackingRunLoopMode : 界面跟踪mode, 用于ScrollView追踪触摸滑动,保证界面滑动时不受其他mode影响 
  kCFRunLoopCommonModes 

如上代码注册的Timer会被添加到当前RunLoop的kCFRunLoopDefaultMode,但当滑动时,mode会被切换到UITrackingRunLoopMode, 注册在kCFRunLoopDefaultMode的Timer将不会被执行,当停止滑动时又会切换回kCFRunLoopDefaultMode,Timer将会继续被执行。

所以如果要在滑动时Timer继续执行的话,解决的方法其实很简单。

可以把Timer添加到这两个mode中:kCFRunLoopDefaultMode,UITrackingRunLoopMode

let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.add(timer, forMode: .tracking)

或者把该Timer添加到kCFRunLoopCommonModes,这样上面两个mode都可以执行这个Timer

let runLoop = RunLoop.current
runLoop.add(timer, forMode: .common)

最后运行效果

\