如何防止用户手动拖动UISlider时periodicTimeObserver更新进度条?
解决UISlider与periodicTimeObserver的冲突问题
问题背景
实现AVPlayer的periodicTimeObserver后,已用时间标签elapsedTimeLabel和剩余时间标签remainingTimeLabel能正常更新,但手动拖动UISlider时滑块会出现跳动——原因是观察者回调和用户操作同时修改滑块值,导致状态冲突。
现有代码
UISlider初始化代码
private lazy var progressBar: UISlider = { let v = UISlider() v.translatesAutoresizingMaskIntoConstraints = false //v.minimumTrackTintColor = UIColor(named: "PlayerColors") v.isContinuous = false return v }()
periodicTimeObserver代码
player = AVPlayer(playerItem: playerItem) player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in if self.player!.currentItem?.status == .readyToPlay { let currentTime : Float64 = CMTimeGetSeconds(self.player!.currentTime()); let totalTime : Float64 = CMTimeGetSeconds(self.player!.currentItem!.duration); self.progressBar.value = Float(currentTime) self.progressBar.minimumValue = 0 self.progressBar.maximumValue = Float(totalTime) self.elapsedTimeLabel.text = self.stringFromTimeInterval(interval: currentTime) self.remainingTimeLabel.text = self.stringFromTimeIntervalRemaining(interval: totalTime - currentTime) } }
滑块拖动回调代码
@objc func progressScrubbed(_ :UISlider) { let seconds : Int64 = Int64(self.progressBar.value) let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 1) player!.seek(to: targetTime) if player!.rate == 0 { play() } }
解决方案
核心思路是添加状态标记,区分用户是否正在拖动滑块,让观察者仅在非拖动状态下更新滑块。
- 添加拖动状态标记
在类中添加布尔属性,记录用户拖动状态:
private var isScrubbing = false
- 绑定滑块拖动开始/结束事件
修改滑块初始化代码,添加touchDown和touchUp事件来切换状态标记:
private lazy var progressBar: UISlider = { let v = UISlider() v.translatesAutoresizingMaskIntoConstraints = false //v.minimumTrackTintColor = UIColor(named: "PlayerColors") v.isContinuous = false // 监听拖动开始 v.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown) // 监听拖动结束(包括松手、滑出滑块区域) v.addTarget(self, action: #selector(scrubbingEnded), for: [.touchUpInside, .touchUpOutside]) return v }()
实现事件回调:
@objc private func scrubbingStarted() { isScrubbing = true } @objc private func scrubbingEnded() { isScrubbing = false }
- 修改观察者回调逻辑
在观察者回调中,仅当isScrubbing为false时更新滑块值:
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in if self.player!.currentItem?.status == .readyToPlay { let currentTime : Float64 = CMTimeGetSeconds(self.player!.currentTime()); let totalTime : Float64 = CMTimeGetSeconds(self.player!.currentItem!.duration); // 仅在非拖动状态下更新滑块 if !self.isScrubbing { self.progressBar.value = Float(currentTime) } // 滑块最大/最小值可只设置一次,建议移到playerItem准备完成时处理 self.progressBar.minimumValue = 0 self.progressBar.maximumValue = Float(totalTime) self.elapsedTimeLabel.text = self.stringFromTimeInterval(interval: currentTime) self.remainingTimeLabel.text = self.stringFromTimeIntervalRemaining(interval: totalTime - currentTime) } }
额外优化建议
- 滑块的
minimumValue和maximumValue无需每次观察者回调都赋值,可放在playerItem状态变为.readyToPlay时一次性设置,减少冗余操作。 - 若需要更流畅的拖动体验,可将
isContinuous设为true,拖动时会持续触发progressScrubbed,但需注意seek操作的性能开销。
内容的提问来源于stack exchange,提问作者Vugar
相关产品推荐
相关产品推荐

