I have a view which asynchronously loads a video and presents it via another UIViewRepresentable
view. GeometryReader
provides the video container's frame which is then used to initialize an AVPlayerLayer
. A problem arises when the device orientation is changed (thus, the container's frame is changed too), and AVPlayerLayer
doesn't relayout itself to match the container. I tried to update the layer's frame within updateUIView
, even call setNeedsLayout
and setNeedsDisplay
to both the layer and uiView
, but nothing changed.
How to adapt the layer to match the size of the container?
struct VideoView: View {
@StateObject var videoLoader: VideoLoader
@ViewBuilder
var body: some View {
if let player = videoLoader.player {
GeometryReader { geometry in
let frame = geometry.frame(in: .local)
VideoPlayerView(frame: frame, player: player)
.frame(width: frame.width, height: frame.height)
.onAppear { player.play() }
.onDisappear { player.pause() }
}
}
}
}
struct VideoPlayerView: UIViewRepresentable {
private let frame: CGRect
private let playerLayer: AVPlayerLayer
init(frame: CGRect, player: AVPlayer) {
self.frame = frame
self.playerLayer = AVPlayerLayer(player: player)
self.playerLayer.frame = frame
}
func makeUIView(context: Context) -> UIView {
let container = UIView()
container.layer.addSublayer(playerLayer)
return container
}
func updateUIView(_ uiView: UIView, context: Context) {
playerLayer.frame.size = uiView.frame.size
}
}
The solution is to create a custom UIView
and update the layer's frame size in layoutSubviews()
.
struct VideoPlayerView: UIViewRepresentable {
class PlayerContainer: UIView {
init(playerLayer: AVPlayerLayer) {
super.init(frame: .zero)
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.first?.frame = frame
}
}
private let playerLayer: AVPlayerLayer
init(player: AVPlayer) {
playerLayer = AVPlayerLayer(player: player)
}
func makeUIView(context: Context) -> UIView {
return PlayerContainer(playerLayer: playerLayer)
}
func updateUIView(_ uiView: UIView, context: Context) { }
}