如何实现带透明区域的全屏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
相关产品推荐
相关产品推荐

