Swift中点击TextField后计时器停止的原因及解决咨询
TableViewHeader计时器点击TextField停止的问题解决
问题原因
根本原因是RunLoop模式切换:默认通过Timer.scheduledTimer创建的计时器,会被添加到RunLoop的DefaultRunLoopMode下。当点击TextField使其成为第一响应者时,系统会将RunLoop切换到UITrackingRunLoopMode(专门处理触摸交互、滚动等操作),此时DefaultRunLoopMode下的计时器会暂停触发,表现为计时器“停止”。
不需要单独线程管理计时器,因为UI操作必须在主线程执行,计时器本身也应该在主线程运行,单独线程反而会引发UI更新的线程安全问题。
解决方案
修改WorkoutTimerManager的核心逻辑,确保计时器在RunLoop的通用模式下运行,同时修正计时起点的问题:
class WorkoutTimerManager { static let shared = WorkoutTimerManager() private var sec = 0 private var min = 0 private var timer: Timer? // 改为可选类型,避免初始空Timer的无效问题 private var date: Date? var timerLbl: UILabel? private init() {} func startTimer() { stopTimer() // 先停止已有计时器,避免重复启动 date = Date() // 重置计时起始时间 timer = Timer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) // 将计时器添加到RunLoop的commonModes,确保交互时也能触发 if let timer = timer { RunLoop.current.add(timer, forMode: .commonModes) } } func stopTimer() { timer?.invalidate() timer = nil } @objc private func updateTimer() { guard let date = date, let timerLbl = timerLbl else { return } let elapsedSec = abs(Int(date.timeIntervalSinceNow)) sec = elapsedSec % 60 min = elapsedSec / 60 // 明确在主线程更新UI,保障安全性 DispatchQueue.main.async { timerLbl.text = String(format: "%02d:%02d", self.min, self.sec) } } }
额外优化建议
- 避免在
tableView(_:viewForHeaderInSection:)中重复启动计时器:TableView的HeaderView可能会因滚动被复用,每次创建Header都调用startTimer()会导致计时器多次启动。建议在ViewController的viewDidLoad或业务逻辑触发时启动计时器,而非Header创建时。 - 管理HeaderView的引用:当HeaderView被销毁时,清空
WorkoutTimerManager.shared.timerLbl的引用,避免野指针和不必要的UI更新。
内容的提问来源于stack exchange,提问作者user10469417
相关产品推荐
相关产品推荐

