66 lines
2.4 KiB
Swift
66 lines
2.4 KiB
Swift
import SwiftUI
|
|
import VisionKit
|
|
|
|
/// Wraps `VNDocumentCameraViewController` (the system document-scanning
|
|
/// camera UI) for SwiftUI. Only the first scanned page is used for v1 — good
|
|
/// enough for a single-receipt photo, and the backend already accepts a
|
|
/// single image per `/api/scan` call.
|
|
///
|
|
/// IMPORTANT: `VNDocumentCameraViewController.isSupported` is `false` on the
|
|
/// Simulator and on devices without a usable camera. Callers must check that
|
|
/// before presenting this view and fall back to the photo picker instead —
|
|
/// this type does not check it itself.
|
|
struct DocumentCameraView: UIViewControllerRepresentable {
|
|
/// Called exactly once with the captured page, or `nil` if the user
|
|
/// cancelled or the scan failed. Never left uncalled, so the caller never
|
|
/// hangs waiting on a result.
|
|
var onComplete: (UIImage?) -> Void
|
|
|
|
func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
|
|
let controller = VNDocumentCameraViewController()
|
|
controller.delegate = context.coordinator
|
|
return controller
|
|
}
|
|
|
|
func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {
|
|
// No dynamic updates needed.
|
|
}
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
Coordinator(onComplete: onComplete)
|
|
}
|
|
|
|
final class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
private let onComplete: (UIImage?) -> Void
|
|
|
|
init(onComplete: @escaping (UIImage?) -> Void) {
|
|
self.onComplete = onComplete
|
|
}
|
|
|
|
func documentCameraViewController(
|
|
_ controller: VNDocumentCameraViewController,
|
|
didFinishWith scan: VNDocumentCameraScan
|
|
) {
|
|
let image = scan.pageCount > 0 ? scan.imageOfPage(at: 0) : nil
|
|
controller.dismiss(animated: true) {
|
|
self.onComplete(image)
|
|
}
|
|
}
|
|
|
|
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
|
|
controller.dismiss(animated: true) {
|
|
self.onComplete(nil)
|
|
}
|
|
}
|
|
|
|
func documentCameraViewController(
|
|
_ controller: VNDocumentCameraViewController,
|
|
didFailWithError error: Error
|
|
) {
|
|
controller.dismiss(animated: true) {
|
|
self.onComplete(nil)
|
|
}
|
|
}
|
|
}
|
|
}
|