SwiftUI中如何为VideoPlayer的有效视频区域添加Overlay
问题:SwiftUI中让VideoPlayer的Overlay仅约束在有效视频区域内
需求说明
我正在使用SwiftUI设计包含VideoPlayer的视图,需要为其videoOverlay:参数添加一个动态UIImageView(为简化测试当前使用本地PNG,实际该UIImageView将来自跨视图共享的Observable对象)
初始实现代码
首先定义了一个UIViewRepresentable:
struct MyViewRepresentable: UIViewRepresentable { typealias UIViewType = UIImageView func updateUIView(_ uiView: UIImageView, context: Context) { } func makeUIView(context: Context) -> UIImageView { let imageView = UIImageView() imageView.image = UIImage(named: "tv-test-pattern.png")! imageView.alpha = 0.3 return imageView } }
然后声明播放器视图:
struct MyVideoPlayer: View { //@ObservedObject var sharedData: SharedData var body: some View { var v = VideoPlayer(player: AVPlayer(url: URL(string: "https://myurltoanvideo.mp4")!), videoOverlay: { GeometryReader { geometry in MyViewRepresentable() .frame(width: geometry.size.width, height: geometry.size.height) .clipped() } }) v } }
当前问题
目前PNG会覆盖整个视图(包括有效视频周围的空白区域):
我希望该图案仅约束在有效视频区域内,而非整个视图。在旧版Swift+UIKit项目中,我曾使用AVPlayerViewController组件,手动将UIImageView作为子视图添加并设置NSLayoutConstraint实现过。
尝试的解决方案(更新)
我尝试用AVPlayerViewController封装成UIViewControllerRepresentable,但遇到问题:PNG默认显示在模拟器左上角,取消注释约束代码后出现两种情况:
- 选项1:图片被拉伸至整个模拟器
- 选项2:图片消失
代码实现
struct VideoOverlay: UIViewControllerRepresentable { var player: AVPlayer @ObservedObject var sharedData: SharedData func makeUIViewController(context: Context) -> AVPlayerViewController { sharedData.videoViewCtrl = AVPlayerViewController() sharedData.videoViewCtrl.player = player sharedData.videoViewCtrl.view.backgroundColor = UIColor.gray sharedData.videoViewCtrl.view.isHidden = false let playerContentOverlayView = sharedData.videoViewCtrl.contentOverlayView! sharedData.imageView = UIImageView() guard let image = UIImage(named: "overlay.png") else { print("Image not found") return sharedData.videoViewCtrl } sharedData.imageView?.image = image sharedData.imageView?.alpha = 1 sharedData.imageView?.isHidden = false sharedData.imageView?.translatesAutoresizingMaskIntoConstraints = false sharedData.videoViewCtrl.contentOverlayView?.addSubview(sharedData.imageView!) // Define constraints programmatically using the video bounds area //OPTION 1 /*NSLayoutConstraint.activate([ sharedData.imageView!.centerXAnchor.constraint(equalTo: playerContentOverlayView.centerXAnchor), sharedData.imageView!.centerYAnchor.constraint(equalTo: playerContentOverlayView.centerYAnchor), sharedData.imageView!.widthAnchor.constraint(equalTo: playerContentOverlayView.widthAnchor), sharedData.imageView!.heightAnchor.constraint(equalTo: playerContentOverlayView.heightAnchor) ])*/ //OPTION 2 /*let quickMarkViewWidthConstraint = NSLayoutConstraint(item: sharedData.imageView!, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: sharedData.videoViewCtrl.videoBounds.width) let quickMarkViewHeightConstraint = NSLayoutConstraint(item: sharedData.imageView!, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: sharedData.videoViewCtrl.videoBounds.height) let centerXQuickMarkContainerView = NSLayoutConstraint(item: playerContentOverlayView, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: sharedData.imageView, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0) let centerYQuickMarkContainerView = NSLayoutConstraint(item: playerContentOverlayView, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: sharedData.imageView, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1, constant: 0) NSLayoutConstraint.activate([centerXQuickMarkContainerView, centerYQuickMarkContainerView, quickMarkViewWidthConstraint, quickMarkViewHeightConstraint]) // Add constraints playerContentOverlayView.addConstraints([centerXQuickMarkContainerView, centerYQuickMarkContainerView, quickMarkViewWidthConstraint, quickMarkViewHeightConstraint])*/ player.play() return sharedData.videoViewCtrl } func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) { } } struct QMVideoPlayer: View { @ObservedObject var sharedData: SharedData var body: some View { let player = AVPlayer(url: URL(string: "https://test.mp4")!) VideoOverlay(player: player, sharedData: sharedData) } }
后续尝试(更新)
我又尝试了简化的约束代码,但添加宽度约束后图片仍然消失:
// Define constraints programmatically using the video bounds area let xc = sharedData.imageView!.centerXAnchor.constraint(equalTo: playerContentOverlayView.centerXAnchor) let yc = sharedData.imageView!.centerYAnchor.constraint(equalTo: playerContentOverlayView.centerYAnchor) // Define constraints programmatically let wc = sharedData.imageView!.widthAnchor.constraint(equalToConstant: sharedData.videoViewCtrl.videoBounds.width) //sharedData.quickMarkViewHeightConstraint = sharedData.imageView!.heightAnchor.constraint(equalToConstant: sharedData.videoViewCtrl.videoBounds.height) NSLayoutConstraint.activate([xc, yc, wc])
最终疑问
是否可以仅用SwiftUI代码实现该效果,还是必须使用基于AVPlayerViewController的RepresentableView?
内容的提问来源于stack exchange,提问作者alexbuisson
相关产品推荐
相关产品推荐

