Anton Tropashko
Anton Tropashko

Reputation: 5816

swiftui: How to catch onWillDisappear

I have google's YTPlayerView leveraged wrapped in UIViewRepresentable in a swiftui project. When user leaves the view with YTPlayerView in progress audio keeps playing in the background. So I need to call stopVideo() on YTPlayerView wrapped instance when this happens. Given that UIViewRepresentable has no view lifecycle management callbacks that I know of I hooked up onDisappear in the View utilizing YTPlayerView as its child. However I get

Accessing StateObject's object without being installed on a View. This will create a new instance each time.

Cause stopVideo is called after the view is already gone. Is there an equivalent of UIKit viewWillDisappear to make the call then the view is still onscreen? ("installed on a View" in SwiftUI newspeak that is)

The ugliness:

struct YTPlayer: UIViewRepresentable, Stoppable {
    internal init(observer: YTPlayerViewDelegateObserverProxy, videoID: String, model: PlayerViewModel) {
        self.observer = observer
        self.videoID = videoID
        self._model = StateObject(wrappedValue: model)
        model.ytplayer = self
    }
    
    @State var observer: YTPlayerViewDelegateObserverProxy
    let videoID: String
    static var cache = NSCache<NSString, YTPlayerView>()
    @StateObject private var model: PlayerViewModel
    
    func makeUIView(context: Context) -> YTPlayerView {
        if let pv = YTPlayer.cache.object(forKey: videoID as NSString) {
            return pv
        }
        let view = YTPlayerView()
        YTPlayer.cache.setObject(view, forKey: videoID as NSString)
        return view
    }

    func updateUIView(_ player: YTPlayerView, context: Context) {
        guard player.delegate !== observer else {
            return
        }
        model.ytplayerview = player
        let playerVars = ["playsinline" : NSNumber(1)]
        player.delegate = observer
        player.load(withVideoId: videoID, playerVars: playerVars)
    }
    
    static func dismantleUIView(player: YTPlayerView, coordinator: Self.Coordinator)
    {
        player.stopVideo()
    }
    
    func stop() {
        guard let player = model.ytplayerview as? YTPlayerView else {
            assertionFailure()
            return
        }
        player.stopVideo()
    }
    func anyview() -> AnyView {
        AnyView(self)
    }
}

enter image description here

Even more ugliness:

final public class PlayerViewModel: NSObject, ObservableObject {
...
    public var ytplayerview: UIView?
    public var ytplayer: Stoppable?



public protocol Stoppable
{
    func stop()
    // I know, pathetic, I moved it to the Lincoln street...
    func anyview() -> AnyView
}

Upvotes: -1

Views: 454

Answers (1)

malhal
malhal

Reputation: 30746

You need to upgrade to UIViewControllerRepresentable with a custom UIViewController subclass that has YTPlayerView as its view to get access to viewWillDisappear and viewDidDisappear.

As of 18.4 beta the appearance methods is now received in representables inside containers like List, TabView etc.

Upvotes: 0

Related Questions