You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 03:45:53