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

SwiftUI视图层级中使用AVAudioPlayerDelegate时设置@EnvironmentObject值

解决SwiftUI中音频播放完成后修改@EnvironmentObject的问题

思路一:给代理类添加回调闭包

让MyPlayerDelegate支持闭包回调,在视图中创建代理时,将修改settings的逻辑通过闭包传入,音频播放完成时触发闭包即可修改环境变量。

修改后的完整代码:

import SwiftUI
import AVFoundation
import MediaPlayer
import AVKit

struct MeditationView: View {
    @EnvironmentObject var settings: Settings
    
    // 用@StateObject管理代理生命周期,确保和视图同步
    @StateObject private var myPlayerDelegate = MyPlayerDelegate()
    
    var body: some View {
        ZStack {
            settings.rainbowColour
                .animation(.easeInOut(duration: 1))
            VStack {
                meditationImage(colour: settings.rainbowColour)
                    .resizable()
                    .opacity(settings.showRainbow ? 0 : 1)
                    .scaledToFit()
                    .onAppear() {
                        // 提前设置播放完成后的回调逻辑
                        myPlayerDelegate.onPlaybackFinished = { [weak self] in
                            guard let self = self else { return }
                            self.settings.showRainbow = true
                        }
                        
                        if let audioFileURL = Bundle.main.url(forResource: settings.meditation, withExtension: "mp3") {
                            do {
                                let audioPlayer = try AVAudioPlayer(contentsOf: audioFileURL)
                                audioPlayer.prepareToPlay()
                                audioPlayer.delegate = myPlayerDelegate
                                audioPlayer.play()
                                // 将播放器存到代理中,方便后续暂停操作
                                myPlayerDelegate.player = audioPlayer
                            } catch let error {
                                print("无法播放冥想音频\(settings.meditation)")
                                print(error.localizedDescription)
                            }
                        } else {
                            print("无法加载音频文件: \(settings.meditation)")
                        }
                    }
                    .onTapGesture {
                        myPlayerDelegate.player?.pause()
                        settings.showRainbow = true
                    }
                Text("点击返回彩虹界面")
            }
        }
    }
    
    class MyPlayerDelegate : NSObject, AVAudioPlayerDelegate {
        // 弱引用持有播放器,避免内存泄漏
        weak var player: AVAudioPlayer?
        // 定义播放完成的回调闭包
        var onPlaybackFinished: (() -> Void)?
        
        func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
            guard flag else { return }
            // 触发回调
            onPlaybackFinished?()
        }
    }
    
    // 示例冥想图片视图
    func meditationImage(colour: Color) -> some View {
        Image(systemName: "headphones")
            .foregroundColor(colour)
    }
}

// 示例Settings环境对象类
class Settings: ObservableObject {
    @Published var meditation: String = "sample"
    @Published var rainbowColour: Color = .blue
    @Published var showRainbow: Bool = false
}

思路二:让代理类持有Settings的弱引用

给MyPlayerDelegate添加弱引用的settings属性,在视图初始化代理时传入环境对象,播放完成后直接通过该引用修改值。注意用weak避免循环引用。

核心代码修改:

class MyPlayerDelegate : NSObject, AVAudioPlayerDelegate {
    weak var settings: Settings?
    weak var player: AVAudioPlayer?
    
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        guard flag else { return }
        settings?.showRainbow = true
    }
}

// 视图中初始化并传入settings
struct MeditationView: View {
    @EnvironmentObject var settings: Settings
    @StateObject private var myPlayerDelegate: MyPlayerDelegate
    
    init() {
        let delegate = MyPlayerDelegate()
        _myPlayerDelegate = StateObject(wrappedValue: delegate)
    }
    
    var body: some View {
        ZStack {
            // 原有视图内容...
        }
        .onAppear {
            // 传入环境对象
            myPlayerDelegate.settings = settings
            // 初始化播放器逻辑...
        }
    }
}

额外优化建议

  • 移除全局audioPlayer变量,改用代理或视图的@State属性持有,避免多视图共享播放器导致的异常。
  • 用@StateObject管理代理生命周期,确保代理不会被提前释放,保证回调能正常触发。
  • 播放完成时判断flag参数,仅在播放成功时执行修改操作。

内容的提问来源于stack exchange,提问作者Andromeda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:57:15