You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 4(Xcode 9):UITextView拖动时如何保持Timer动画流畅播放?

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:15:20