如何用SwiftUI实现带圆角边框的身份证取景拍照功能?
身份证拍照添加圆角取景框技术指导求助
我已经实现了身份证拍照功能,但需要给用户添加如图所示的圆角边框取景框辅助拍摄,试了好几种方法都没成功,求技术指导。

当前实现代码:
import UIKit import SwiftUI import AVFoundation struct CameraView: UIViewControllerRepresentable { @Binding var isShowing: Bool @Binding var capturedImage: UIImage? func makeUIViewController(context: UIViewControllerRepresentableContext<CameraView>) -> UIViewController { let cameraView = UIImagePickerController() cameraView.sourceType = .camera cameraView.delegate = context.coordinator return cameraView } func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<CameraView>) { } func makeCoordinator() -> Coordinator { return Coordinator(isShowing: $isShowing, capturedImage: $capturedImage) } class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { @Binding var isShowing: Bool @Binding var capturedImage: UIImage? init(isShowing: Binding<Bool>, capturedImage: Binding<UIImage?>) { _isShowing = isShowing _capturedImage = capturedImage } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage capturedImage = image isShowing = false } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { isShowing = false } } }
解决方案
你现在用的UIImagePickerController是系统封装的相机控件,它不支持在预览画面核心层添加自定义遮罩,这就是你尝试多种方法无效的原因。要实现这种圆角取景框,必须改用AVFoundation框架手动搭建相机界面,这样能完全控制UI层级。
完整实现代码
import UIKit import SwiftUI import AVFoundation struct CustomCameraView: UIViewControllerRepresentable { @Binding var isShowing: Bool @Binding var capturedImage: UIImage? func makeUIViewController(context: Context) -> CustomCameraViewController { let vc = CustomCameraViewController() vc.captureDelegate = context.coordinator return vc } func updateUIViewController(_ uiViewController: CustomCameraViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(isShowing: $isShowing, capturedImage: $capturedImage) } class Coordinator: NSObject, CustomCameraCaptureDelegate { @Binding var isShowing: Bool @Binding var capturedImage: UIImage? init(isShowing: Binding<Bool>, capturedImage: Binding<UIImage?>) { _isShowing = isShowing _capturedImage = capturedImage } func didCaptureImage(_ image: UIImage) { capturedImage = image isShowing = false } func didCancelCapture() { isShowing = false } } } // 自定义相机控制器 class CustomCameraViewController: UIViewController { var captureSession: AVCaptureSession! var previewLayer: AVCaptureVideoPreviewLayer! var captureDelegate: CustomCameraCaptureDelegate? override func viewDidLoad() { super.viewDidLoad() setupCaptureSession() setupPreviewLayer() setupOverlayView() setupCaptureButton() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if !captureSession.isRunning { captureSession.startRunning() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) if captureSession.isRunning { captureSession.stopRunning() } } // 配置相机会话 private func setupCaptureSession() { captureSession = AVCaptureSession() captureSession.sessionPreset = .photo guard let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { fatalError("无法访问后置摄像头") } do { let input = try AVCaptureDeviceInput(device: backCamera) if captureSession.canAddInput(input) { captureSession.addInput(input) } let output = AVCapturePhotoOutput() if captureSession.canAddOutput(output) { captureSession.addOutput(output) } } catch { print("相机配置失败:\(error.localizedDescription)") } } // 添加预览层 private func setupPreviewLayer() { previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) previewLayer.videoGravity = .resizeAspectFill previewLayer.frame = view.bounds view.layer.addSublayer(previewLayer) } // 添加圆角取景遮罩 private func setupOverlayView() { let overlayView = UIView(frame: view.bounds) // 周围半透明黑色遮罩 overlayView.backgroundColor = UIColor.black.withAlphaComponent(0.7) let maskLayer = CAShapeLayer() maskLayer.frame = overlayView.bounds // 身份证比例:85.6mm × 54mm ≈ 1.585:1 let aspectRatio: CGFloat = 85.6 / 54 let maxWidth = view.bounds.width * 0.8 let frameWidth = min(maxWidth, view.bounds.height / aspectRatio * 0.8) let frameHeight = frameWidth / aspectRatio let x = (view.bounds.width - frameWidth) / 2 let y = (view.bounds.height - frameHeight) / 2 // 创建路径:先画全屏矩形,再减去中间的圆角矩形,实现镂空效果 let fullPath = UIBezierPath(rect: overlayView.bounds) let roundedRectPath = UIBezierPath(roundedRect: CGRect(x: x, y: y, width: frameWidth, height: frameHeight), cornerRadius: 16) fullPath.append(roundedRectPath) fullPath.usesEvenOddFillRule = true maskLayer.path = fullPath.cgPath maskLayer.fillRule = .evenOdd overlayView.layer.mask = maskLayer view.addSubview(overlayView) } // 添加拍照按钮 private func setupCaptureButton() { let captureButton = UIButton(type: .system) captureButton.setTitle("拍照", for: .normal) captureButton.setTitleColor(.white, for: .normal) captureButton.backgroundColor = .systemBlue captureButton.layer.cornerRadius = 25 captureButton.frame = CGRect(x: (view.bounds.width - 100)/2, y: view.bounds.height - 80, width: 100, height: 50) captureButton.addTarget(self, action: #selector(capturePhoto), for: .touchUpInside) view.addSubview(captureButton) let cancelButton = UIButton(type: .system) cancelButton.setTitle("取消", for: .normal) cancelButton.setTitleColor(.white, for: .normal) cancelButton.frame = CGRect(x: 20, y: view.safeAreaInsets.top + 20, width: 60, height: 30) cancelButton.addTarget(self, action: #selector(cancelCapture), for: .touchUpInside) view.addSubview(cancelButton) } @objc private func capturePhoto() { guard let photoOutput = captureSession.outputs.first as? AVCapturePhotoOutput else { return } let settings = AVCapturePhotoSettings() photoOutput.capturePhoto(with: settings, delegate: self) } @objc private func cancelCapture() { captureDelegate?.didCancelCapture() } } // 拍照代理协议 protocol CustomCameraCaptureDelegate: NSObjectProtocol { func didCaptureImage(_ image: UIImage) func didCancelCapture() } extension CustomCameraViewController: AVCapturePhotoCaptureDelegate { func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("拍照失败:\(error.localizedDescription)") return } if let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) { captureDelegate?.didCaptureImage(image) } } } // 使用示例 struct CameraDemoView: View { @State private var showCamera = false @State private var photo: UIImage? var body: some View { VStack { if let photo = photo { Image(uiImage: photo) .resizable() .scaledToFit() } Button("打开相机") { showCamera = true } } .sheet(isPresented: $showCamera) { CustomCameraView(isShowing: $showCamera, capturedImage: $photo) .ignoresSafeArea() } } }
关键说明
- 改用AVFoundation:摆脱系统
UIImagePickerController的限制,完全自定义相机UI - 遮罩层实现:通过
CAShapeLayer创建镂空的圆角矩形遮罩,周围半透明黑色,突出中间取景区域 - 身份证比例适配:按照标准身份证的宽高比(≈1.585:1)计算取景框尺寸,保证适配不同屏幕
- SwiftUI集成:通过
UIViewControllerRepresentable将自定义相机控制器包装成SwiftUI可使用的视图
内容的提问来源于stack exchange,提问作者Dan709
相关产品推荐
相关产品推荐

