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

如何防止用户手动拖动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()
    }
}

解决方案

核心思路是添加状态标记,区分用户是否正在拖动滑块,让观察者仅在非拖动状态下更新滑块。

  1. 添加拖动状态标记
    在类中添加布尔属性,记录用户拖动状态:
private var isScrubbing = false
  1. 绑定滑块拖动开始/结束事件
    修改滑块初始化代码,添加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
}
  1. 修改观察者回调逻辑
    在观察者回调中,仅当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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 16:45:42