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?