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
相关产品推荐
相关产品推荐

