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

如何用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()
        }
    }
}

关键说明

  1. 改用AVFoundation:摆脱系统UIImagePickerController的限制,完全自定义相机UI
  2. 遮罩层实现:通过CAShapeLayer创建镂空的圆角矩形遮罩,周围半透明黑色,突出中间取景区域
  3. 身份证比例适配:按照标准身份证的宽高比(≈1.585:1)计算取景框尺寸,保证适配不同屏幕
  4. SwiftUI集成:通过UIViewControllerRepresentable将自定义相机控制器包装成SwiftUI可使用的视图

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 11:01:36