How to pass data from UIViewControllerRepresentable to parent SwiftUI view? (toy example inside)

23 views Asked by At

Toy example:

We define a view to be 200 width X 200 height.

Inside will be a video player.

However, we don't know the video that might be loaded. It could be 16:9, 3:4, 1:1 etc.

When we load our view in, we may want to put a view over exactly the portion of the AVPlayer that actually is displaying video [and not the additional black parts].

For example, if you load a 16:9 video into an AVPlayer view that is 200x200, you'll end up with an AVPlayer that is 200x200, but that portion playing the video will be ~200x112.666.

(Side note: Please solve the specific question as to passing data back to a SwiftUI view, as it is the question. Making this simpler toy example below has likely lost context as to why it's really needed)


SwiftUI View:

struct VideoWithPossibleVideoBoundedOverlayView: View {
    @State var player = AVPlayer(url: URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!)
    @State var playerVideoBounds: CGRect = .zero
    @State var forceUpdateToggle: Bool = false
    
    var body: some View {
        PlayerController(player: $player, playerVideoBounds: $playerVideoBounds, forceUpdateToggle: $forceUpdateToggle)
            .frame(width: 200, height: 200)
            .onAppear {
                player.play()
            }
            .onTapGesture {
                //make a silly action that allows us to force our UIViewRepresentable to update for this example
                forceUpdateToggle.toggle()
            }
    }
}

UIViewControllerRepresentable

struct PlayerController: UIViewControllerRepresentable {
    typealias UIViewControllerType = AVPlayerViewController
    
    @Binding var player: AVPlayer
    @Binding var playerVideoBounds: CGRect //we want to pass back videoBounds here!
    @Binding var forceUpdateToggle: Bool
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<PlayerController>) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player
        controller.showsPlaybackControls = false
        return controller
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<PlayerController>) {
        if uiViewController.player != player {
            uiViewController.player = player
        }
        
        print("\nforceUpdateToggle: \(forceUpdateToggle)")
        print("VideoBounds: \(uiViewController.videoBounds)") //AVPlayerViewController.videoBounds is the way to get the "viewable" bounds of the video itself. I.E. 200 x 112.6666 in our example [i.e. 16:9]. Now we want to pass this back to the SwiftUI View `VideoWithPossibleVideoBoundedOverlayView` as we may want to overlay a view with only that specific size
        
        playerVideoBounds = uiViewController.videoBounds
        context.coordinator.parent.playerVideoBounds = uiViewController.videoBounds
        print("playerVideoBounds: \(playerVideoBounds)")
        print("context.coordinator.parent.playerVideoBounds: \(context.coordinator.parent.playerVideoBounds)")
        
        context.coordinator.updateVideoBounds(videoBounds: uiViewController.videoBounds)
        print("playerVideoBounds: \(playerVideoBounds)")
        print("context.coordinator.parent.playerVideoBounds: \(context.coordinator.parent.playerVideoBounds)")
    }
    
    class Coordinator: NSObject, AVPlayerViewControllerDelegate {
        var parent: PlayerController

        init(_ parent: PlayerController) {
            self.parent = parent
        }
        
        func updateVideoBounds(videoBounds: CGRect) {
            parent.playerVideoBounds = videoBounds
            print("parent.playerVideoBounds: \(parent.playerVideoBounds)")
        }
    }
}

All results for attempting to pass back the value via settings the binding end up with no updated video bounds. Here are the printed results below.

forceUpdateToggle: false
VideoBounds: (0.0, 43.666666666666664, 200.0, 112.66666666666667)
playerVideoBounds: (0.0, 0.0, 0.0, 0.0)
context.coordinator.parent.playerVideoBounds: (0.0, 0.0, 0.0, 0.0)
parent.playerVideoBounds: (0.0, 0.0, 0.0, 0.0)
playerVideoBounds: (0.0, 0.0, 0.0, 0.0)
context.coordinator.parent.playerVideoBounds: (0.0, 0.0, 0.0, 0.0)

So the question is... how can one pass data from a UIViewControllerRepresentable to it's parent SwiftUI view?

0

There are 0 answers