滑动UIScrollView时内部绑定计时器的视图动画停止运行如何解决
问题根本原因
这个问题和视图层级没有关系,是iOS的RunLoop运行机制导致的:
- 默认创建的Timer默认被添加到主线程RunLoop的
default模式下,只有RunLoop处于default模式时Timer才会触发 - 当滑动UIScrollView时,主线程RunLoop会自动切换到
UITrackingRunLoopMode(专门用来处理滑动交互,保证滑动流畅),此时default模式下的所有事件源(包括你创建的Timer)都会被暂时暂停,直到滑动结束RunLoop切回default模式才会恢复。
可行解决方案
方案1:修改Timer的RunLoop添加模式
把Timer添加到RunLoop的common模式下,common模式是模式集合的标记,会同时在default和UITrackingRunLoopMode下生效,滑动时也能正常触发Timer。
代码示例:
// 不要直接用scheduledTimer自动添加,先创建Timer实例 let timer = Timer(timeInterval: 1, target: self, selector: #selector(lowBeatingAnimation), userInfo: nil, repeats: true) // 手动添加到主线程RunLoop的common模式 RunLoop.main.add(timer, forMode: .common)
iOS10以上的block写法Timer逻辑一致,先创建实例再手动添加到对应RunLoop模式即可。
方案2:使用GCD定时器(更推荐)
GCD的定时器不依赖RunLoop,完全不受RunLoop模式切换的影响,精度也更高,是更稳定的方案。
代码示例:
// 需在当前类声明GCD定时器属性,持有对象避免被释放 var gcdTimer: DispatchSourceTimer? // 初始化定时器 gcdTimer = DispatchSource.makeTimerSource(queue: .main) // 设置每秒触发一次,允许的误差0.1秒(可根据业务需求调整) gcdTimer?.schedule(deadline: .now(), repeating: 1, leeway: .milliseconds(100)) // 设置触发回调 gcdTimer?.setEventHandler { [weak self] in self?.lowBeatingAnimation() } // 启动定时器 gcdTimer?.resume() // 不需要时记得销毁定时器避免内存泄漏 // gcdTimer?.cancel() // gcdTimer = nil
方案3:补充动画配置避免滑动时动画中断
即使Timer能正常触发,滑动时默认的动画也可能被交互打断,可以给你的动画添加两个配置参数,保证滑动时动画正常执行:
把你的动画代码的options参数做如下修改:
- 原
.curveEaseIn改为[.curveEaseIn, .allowUserInteraction, .beginFromCurrentState] - 原
.curveEaseOut改为[.curveEaseOut, .allowUserInteraction, .beginFromCurrentState]
参数说明:
.allowUserInteraction:允许动画执行期间响应交互.beginFromCurrentState:如果动画被打断,从当前状态继续执行新的动画,不会出现视觉跳变
内容的提问来源于stack exchange,提问作者Swifter
相关产品推荐
相关产品推荐

