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

如何实现带透明区域的全屏AVCaptureVideoPreviewLayer相机预览?

实现中间不透明、外部半透明的全屏相机预览

无法直接给AVCaptureVideoPreviewLayer设置局部透明度,我们可以通过**遮罩(Mask)**实现需求,以下是两种可行方案:

方案一:UIKit图层遮罩(修改相机控制器)

直接给AVCaptureVideoPreviewLayer添加遮罩层,控制预览区域的透明度:

import SwiftUI
import AVFoundation

class CameraViewController: UIViewController {
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    var maskLayer: CALayer! // 新增遮罩层引用
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .photo
        
        guard let backCamera = AVCaptureDevice.default(for: .video) else { return }
        
        do {
            let input = try AVCaptureDeviceInput(device: backCamera)
            if captureSession.canAddInput(input) {
                captureSession.addInput(input)
            }
        } catch {
            print("Error: Unable to initialize back camera: \(error.localizedDescription)")
        }
        
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.videoGravity = .resizeAspectFill
        previewLayer.frame = view.layer.bounds
        
        view.layer.addSublayer(previewLayer)
        
        // 初始化遮罩层
        setupMaskLayer()
        
        captureSession.startRunning()
    }
    
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // 屏幕旋转时更新遮罩层布局
        updateMaskLayerFrame()
    }
    
    private func setupMaskLayer() {
        maskLayer = CALayer()
        maskLayer.frame = view.layer.bounds
        
        // 外部半透明区域层
        let outerLayer = CAShapeLayer()
        outerLayer.path = UIBezierPath(rect: view.layer.bounds).cgPath
        outerLayer.fillColor = UIColor.black.withAlphaComponent(0.3).cgColor
        
        // 中间不透明区域层(可自定义宽高)
        let rectWidth: CGFloat = 300
        let rectHeight: CGFloat = 400
        let rectOriginX = (view.bounds.width - rectWidth)/2
        let rectOriginY = (view.bounds.height - rectHeight)/2
        let centerRectPath = UIBezierPath(rect: CGRect(x: rectOriginX, y: rectOriginY, width: rectWidth, height: rectHeight))
        
        let innerLayer = CAShapeLayer()
        innerLayer.path = centerRectPath.cgPath
        innerLayer.fillColor = UIColor.black.withAlphaComponent(1).cgColor
        
        maskLayer.addSublayer(outerLayer)
        maskLayer.addSublayer(innerLayer)
        
        // 将遮罩层应用到预览层
        previewLayer.mask = maskLayer
    }
    
    private func updateMaskLayerFrame() {
        guard let maskLayer = maskLayer else { return }
        maskLayer.frame = view.layer.bounds
        
        // 更新外部区域路径
        if let outerLayer = maskLayer.sublayers?.first as? CAShapeLayer {
            outerLayer.path = UIBezierPath(rect: view.layer.bounds).cgPath
        }
        
        // 更新中间区域路径
        if let innerLayer = maskLayer.sublayers?.last as? CAShapeLayer {
            let rectWidth: CGFloat = 300
            let rectHeight: CGFloat = 400
            let rectOriginX = (view.bounds.width - rectWidth)/2
            let rectOriginY = (view.bounds.height - rectHeight)/2
            innerLayer.path = UIBezierPath(rect: CGRect(x: rectOriginX, y: rectOriginY, width: rectWidth, height: rectHeight)).cgPath
        }
    }
}

struct CameraView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> CameraViewController {
        return CameraViewController()
    }
    
    func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        ZStack {
            CameraView()
                .edgesIgnoringSafeArea(.all)
        }
    }
}

方案二:SwiftUI视图遮罩(无需修改相机控制器)

在SwiftUI层级中直接叠加半透明遮罩,视觉效果与方案一一致,实现更简单:

import SwiftUI
import AVFoundation

// 保留你原有的CameraViewController和CameraView代码不变

struct ContentView: View {
    // 自定义中间矩形的宽高
    let targetRectWidth: CGFloat = 300
    let targetRectHeight: CGFloat = 400
    
    var body: some View {
        ZStack {
            CameraView()
                .edgesIgnoringSafeArea(.all)
            
            // 半透明遮罩层,中间挖空显示相机预览
            Color.black.opacity(0.7)
                .mask(
                    // 用组合路径实现中间挖空
                    Rectangle()
                        .fill(Color.black)
                        .overlay(
                            Rectangle()
                                .frame(width: targetRectWidth, height: targetRectHeight)
                                .foregroundColor(.clear)
                        )
                        .compositingGroup()
                        .luminanceToAlpha()
                )
        }
    }
}

两种方案对比:

  • UIKit方案:直接控制相机预览层的透明度,无额外视图层级
  • SwiftUI方案:通过叠加遮罩实现,代码更简洁,适合纯SwiftUI场景

内容的提问来源于stack exchange,提问作者bli00

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:32:06