DocumentScannerView.swift
103 lignes · 3606 octets
import SwiftUI import VisionKit import PhotosUI /// SwiftUI wrapper around `VNDocumentCameraViewController`. /// VisionKit performs capture + perspective correction on-device; /// only the first scanned page is used (one business card per scan). struct DocumentScannerView: UIViewControllerRepresentable { enum Outcome { case page(UIImage) case cancelled case failed(Error) } let completion: (Outcome) -> Void /// False in the simulator — use `PhotoPickerView` there. static var isSupported: Bool { VNDocumentCameraViewController.isSupported } func makeUIViewController(context: Context) -> VNDocumentCameraViewController { let controller = VNDocumentCameraViewController() controller.delegate = context.coordinator return controller } func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(completion: completion) } final class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate { private let completion: (Outcome) -> Void init(completion: @escaping (Outcome) -> Void) { self.completion = completion } func documentCameraViewController( _ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan ) { guard scan.pageCount > 0 else { completion(.cancelled) return } completion(.page(scan.imageOfPage(at: 0))) } func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) { completion(.cancelled) } func documentCameraViewController( _ controller: VNDocumentCameraViewController, didFailWithError error: Error ) { completion(.failed(error)) } } } /// `PHPickerViewController` wrapper so the scan flow can be exercised in the /// simulator with an imported image. Completion receives nil on cancel or when /// the selected asset cannot be loaded as a UIImage. struct PhotoPickerView: UIViewControllerRepresentable { let completion: (UIImage?) -> Void func makeUIViewController(context: Context) -> PHPickerViewController { var configuration = PHPickerConfiguration() configuration.filter = .images configuration.selectionLimit = 1 let controller = PHPickerViewController(configuration: configuration) controller.delegate = context.coordinator return controller } func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(completion: completion) } final class Coordinator: NSObject, PHPickerViewControllerDelegate { private let completion: (UIImage?) -> Void init(completion: @escaping (UIImage?) -> Void) { self.completion = completion } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else { completion(nil) return } let completion = completion provider.loadObject(ofClass: UIImage.self) { object, _ in DispatchQueue.main.async { completion(object as? UIImage) } } } } }
GitRust