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

SwiftUI中如何捕获onWillDisappear以停止YTPlayerView播放

解决SwiftUI中视图消失前停止YTPlayerView的问题

核心问题分析

你遇到的Accessing StateObject's object without being installed on a View错误,根源在于错误地将外部传入的PlayerViewModel用@StateObject修饰。@StateObject是用来管理视图内部创建的ObservableObject实例的,外部传入的对象应该用@ObservedObject,否则视图消失后访问会创建新的实例,导致报错。

解决方案

1. 修正ViewModel的属性修饰符

首先修改YTPlayer结构体中的model属性,将@StateObject改为@ObservedObject:

struct YTPlayer: UIViewRepresentable, Stoppable {
    internal init(observer: YTPlayerViewDelegateObserverProxy, videoID: String, model: PlayerViewModel) {
        self.observer = observer
        self.videoID = videoID
        self.model = model // 移除StateObject初始化逻辑
        model.ytplayer = self
    }
    
    @State var observer: YTPlayerViewDelegateObserverProxy
    let videoID: String
    static var cache = NSCache<NSString, YTPlayerView>()
    @ObservedObject var model: PlayerViewModel // 改为@ObservedObject
    
    // 其余代码保持不变...
}

2. 实现类似viewWillDisappear的回调

SwiftUI没有直接的viewWillDisappear方法,但可以通过以下几种方式实现相同时机的操作:

方式一:利用onDisappear回调(最简单)

修正后,直接在父视图或YTPlayer的包裹视图中使用onDisappear,此时调用stop()方法不会再报错,且执行时机足够早(视图正在消失但尚未完全从层级中移除):

// 父视图示例
struct ParentView: View {
    @State private var showPlayer = false
    @State private var playerModel = PlayerViewModel()
    
    var body: some View {
        NavigationStack {
            Button("播放视频") {
                showPlayer = true
            }
            .navigationDestination(isPresented: $showPlayer) {
                YTPlayer(
                    observer: YTPlayerViewDelegateObserverProxy(),
                    videoID: "你的视频ID",
                    model: playerModel
                )
                .onDisappear {
                    playerModel.ytplayer?.stop()
                }
            }
        }
    }
}
方式二:用UIViewController包裹,复用UIKit生命周期

如果需要完全匹配viewWillDisappear的时机,可以将YTPlayerView封装在自定义UIViewController中,利用UIKit的生命周期方法:

// 自定义承载Player的ViewController
class YTPlayerHostVC: UIViewController {
    var videoID: String!
    var observer: YTPlayerViewDelegateObserverProxy!
    var model: PlayerViewModel!
    private var playerView: YTPlayerView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupPlayer()
    }
    
    private func setupPlayer() {
        // 复用你原有的缓存逻辑
        if let cachedPlayer = YTPlayer.cache.object(forKey: videoID as NSString) {
            playerView = cachedPlayer
        } else {
            playerView = YTPlayerView()
            YTPlayer.cache.setObject(playerView!, forKey: videoID as NSString)
        }
        
        guard let playerView = playerView else { return }
        playerView.frame = view.bounds
        playerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(playerView)
        
        model.ytplayerview = playerView
        let playerVars = ["playsinline" : NSNumber(1)]
        playerView.delegate = observer
        playerView.load(withVideoId: videoID, playerVars: playerVars)
    }
    
    // 完全对应UIKit的viewWillDisappear
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        playerView?.stopVideo()
    }
}

// SwiftUI视图封装
struct YTPlayerContainer: UIViewControllerRepresentable {
    let videoID: String
    let observer: YTPlayerViewDelegateObserverProxy
    let model: PlayerViewModel
    
    func makeUIViewController(context: Context) -> YTPlayerHostVC {
        let vc = YTPlayerHostVC()
        vc.videoID = videoID
        vc.observer = observer
        vc.model = model
        return vc
    }
    
    func updateUIViewController(_ vc: YTPlayerHostVC, context: Context) {}
}

// 父视图中使用
struct ParentView: View {
    @State private var showPlayer = false
    @State private var playerModel = PlayerViewModel()
    
    var body: some View {
        NavigationStack {
            Button("播放视频") {
                showPlayer = true
            }
            .navigationDestination(isPresented: $showPlayer) {
                YTPlayerContainer(
                    videoID: "你的视频ID",
                    observer: YTPlayerViewDelegateObserverProxy(),
                    model: playerModel
                )
            }
        }
    }
}
方式三:监听导航状态变化

如果使用NavigationStack,可以通过监听isPresented绑定值的变化,在视图即将消失时触发停止操作:

struct ParentView: View {
    @State private var showPlayer = false
    @State private var playerModel = PlayerViewModel()
    
    var body: some View {
        NavigationStack {
            Button("播放视频") {
                showPlayer = true
            }
            .navigationDestination(isPresented: $showPlayer) {
                YTPlayer(
                    observer: YTPlayerViewDelegateObserverProxy(),
                    videoID: "你的视频ID",
                    model: playerModel
                )
                .onChange(of: showPlayer) { newValue in
                    // 当showPlayer变为false时,视图即将消失
                    if !newValue {
                        playerModel.ytplayer?.stop()
                    }
                }
            }
        }
    }
}

额外建议

  • 你的dismantleUIView方法因为使用了缓存,YTPlayerView不会被销毁,所以该方法的stopVideo()可能不会在视图消失时生效,建议优先使用上述方案。
  • 如果不需要缓存YTPlayerView,可以在dismantleUIView中移除缓存对象,确保每次创建新的实例:
static func dismantleUIView(player: YTPlayerView, coordinator: Self.Coordinator) {
    player.stopVideo()
    cache.removeObject(forKey: videoID as NSString) // 移除缓存
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:59:58