Swift 4(Xcode 9):UITextView拖动时如何保持Timer动画流畅播放?
嘿,这个问题我做项目时也碰到过,确实是iOS系统的默认机制在搞鬼,我来给你拆解清楚:
为什么动画会暂停?
UITextView继承自UIScrollView,当你拖动滚动视图时,系统会把主线程的RunLoop模式切换到跟踪模式(RunLoop.Mode.tracking)。而你用Timer.scheduledTimer(...)创建的定时器,默认是绑定在RunLoop的**默认模式(RunLoop.Mode.default)**下的。这两个模式互斥——当RunLoop处于跟踪模式时,默认模式下的定时器就会暂停触发,直到你松开手指,RunLoop切回默认模式,定时器才会继续工作。
解决办法
这里给你三种靠谱的方案,从简单到灵活,你可以根据需求选择:
方案1:把Timer加到RunLoop的Common Modes里
Common Modes是一组模式的集合,包含了默认模式和跟踪模式,把Timer加到这里,不管滚动与否,定时器都会正常触发。修改你的代码如下:
var timerCount = 0 // 建议变量名小写开头,符合Swift编码规范 var timer: Timer? // 创建定时器并绑定到Common Modes func startButtonAnimation() { timerCount = 0 timer = Timer.scheduledTimer(withTimeInterval: 0.02, repeats: true) { [weak self] timer in guard let self = self else { timer.invalidate() return } // 这里放你的逐帧动画逻辑,比如调整按钮的缩放比例 self.button.transform = CGAffineTransform(scaleX: 1 + CGFloat(self.timerCount)*0.05, y: 1 + CGFloat(self.timerCount)*0.05) if self.timerCount >= 19 { timer.invalidate() self.timer = nil self.timerCount = 0 } self.timerCount += 1 } // 关键步骤:将定时器添加到RunLoop的Common Modes if let timer = timer { RunLoop.current.add(timer, forMode: .common) } }
注意用[weak self]避免循环引用,防止内存泄漏。
方案2:用CADisplayLink代替Timer(更适合动画)
CADisplayLink是专门为屏幕动画设计的,它会跟屏幕刷新率(通常60fps)同步触发,精度比Timer更高,而且默认就会加入Common Modes,滚动时不会暂停。代码示例:
var displayLink: CADisplayLink? var frameCount = 0 func startButtonAnimation() { frameCount = 0 displayLink = CADisplayLink(target: self, selector: #selector(updateAnimationFrame)) displayLink?.add(to: .current, forMode: .common) } @objc private func updateAnimationFrame() { // 逐帧更新动画 button.transform = CGAffineTransform(scaleX: 1 + CGFloat(frameCount)*0.05, y: 1 + CGFloat(frameCount)*0.05) frameCount += 1 if frameCount >= 19 { displayLink?.invalidate() displayLink = nil frameCount = 0 } }
这个方案更适合UI动画,触发时机完全贴合屏幕刷新,不会出现Timer可能的卡顿或丢帧。
方案3:直接用UIKit的动画API(最简单)
如果你只是做简单的缩放动画,完全不需要自己管理定时器,UIKit自带的动画API会自动处理RunLoop的问题,滚动时也能流畅运行:
func startButtonAnimation() { // 先重置按钮状态 button.transform = .identity // 执行动画:时长0.4秒(0.02*20),线性动画 UIView.animate(withDuration: 0.4, delay: 0, options: .curveLinear) { self.button.transform = CGAffineTransform(scaleX: 2, y: 2) // 最终放大到2倍 } completion: { _ in // 动画结束后可以恢复按钮大小 UIView.animate(withDuration: 0.2) { self.button.transform = .identity } } }
这个方案代码最少,系统会帮你处理所有细节,包括动画流畅性和RunLoop适配,是优先推荐的方式。
内容的提问来源于stack exchange,提问作者Sidney Liu

