SwiftUI中AVAudioPlayer滑块值实时更新实现问题咨询
解决SwiftUI音频播放时滑块实时更新的问题
嘿,我看了你的代码,问题的核心其实很明确——你的audioSettings里的关键变量没有被标记为可观察的,所以SwiftUI不知道playValue在播放过程中变化了,自然不会实时更新滑块!咱们一步步来修复:
第一步:修正audioSettings的ObservableObject属性
ObservableObject只有当属性被@Published标记时,才会把变化通知给视图。你现在的playing、playValue都没加这个标记,这就是滑块不实时动的根本原因。另外,硬编码playerDuration也不太靠谱,最好从音频文件里读取真实时长。
修改后的audioSettings代码:
import Foundation import AVFoundation import Combine class AudioSettings: ObservableObject { // 类名首字母大写,符合Swift规范 @Published var audioPlayer: AVAudioPlayer? @Published var playing = false @Published var playValue: TimeInterval = 0.0 @Published var playerDuration: TimeInterval = 0.0 // 不再硬编码 private var timerCancellable: AnyCancellable? // 用Cancellable管理timer生命周期 func playSound(sound: String, type: String) { guard let path = Bundle.main.path(forResource: sound, ofType: type) else { print("Could not find the sound file.") return } let url = URL(fileURLWithPath: path) do { if audioPlayer == nil { audioPlayer = try AVAudioPlayer(contentsOf: url) audioPlayer?.prepareToPlay() playerDuration = audioPlayer?.duration ?? 0.0 // 读取真实时长 } if !playing { audioPlayer?.play() playing = true startTimer() // 播放时启动timer } } catch { print("Could not play the sound file: \(error.localizedDescription)") } } func stopSound() { audioPlayer?.stop() audioPlayer = nil playing = false playValue = 0.0 stopTimer() // 停止timer } func pauseSound() { if playing { audioPlayer?.pause() playing = false stopTimer() // 暂停时停止timer } } func changeSliderValue() { guard let player = audioPlayer else { return } if playing { pauseSound() } player.currentTime = playValue player.play() playing = true startTimer() // 跳转后重新启动timer } // 封装timer的启动逻辑 private func startTimer() { stopTimer() // 先停止之前的timer,避免重复 timerCancellable = Timer.publish(every: 0.1, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in guard let self = self, let player = self.audioPlayer, self.playing else { self?.stopTimer() return } self.playValue = player.currentTime // 检测播放结束 if player.currentTime >= player.duration { self.stopSound() } } } // 封装timer的停止逻辑 private func stopTimer() { timerCancellable?.cancel() timerCancellable = nil } }
第二步:简化视图代码,移除冗余逻辑
原来的视图里手动管理playButton状态、重新创建timer都是多余的,现在可以直接依赖AudioSettings的@Published属性来驱动UI,让代码更简洁可靠:
修改后的视图代码:
import SwiftUI struct MyExperienceFearChunk: View { // 视图名首字母大写,符合Swift规范 @ObservedObject var audioSettings = AudioSettings() // 不再手动维护按钮状态,直接根据playing判断 private var playButtonImage: Image { audioSettings.playing ? Image(systemName: "pause.circle") : Image(systemName: "play.circle") } var body: some View { VStack { // 用VStack把按钮和滑块垂直排列,更合理 HStack { Button(action: { if audioSettings.playing { audioSettings.pauseSound() } else { audioSettings.playSound(sound: "audio file", type: "mp3") } }) { playButtonImage .foregroundColor(.white) .font(.system(size: 44)) } Button(action: { audioSettings.stopSound() }) { Image(systemName: "stop.circle") .foregroundColor(.white) .font(.system(size: 44)) } } Slider( value: $audioSettings.playValue, in: 0...audioSettings.playerDuration, onEditingChanged: { isEditing in if !isEditing { // 只有当用户结束拖动时才跳转 audioSettings.changeSliderValue() } } ) .disabled(audioSettings.playerDuration == 0) // 音频未加载时禁用滑块 } } }
关键修复点说明
- @Published标记:给
playing、playValue、playerDuration加上@Published,让SwiftUI能感知到这些值的变化,自动刷新视图(包括滑块)。 - Timer生命周期管理:在
audioSettings内部封装timer的启动和停止,避免在视图里重复创建和销毁,逻辑更内聚。 - 移除冗余状态:视图不再手动维护
playButton的状态,直接根据audioSettings.playing动态生成,减少状态不一致的风险。 - 真实时长获取:从
AVAudioPlayer读取音频的真实时长,代替硬编码的146,适配不同音频文件。
这样修改后,播放音频时滑块就会实时跟随playValue的变化更新了!
内容的提问来源于stack exchange,提问作者Joshua Browne
相关产品推荐
相关产品推荐

