feat: add iOS support and harden receipt scanning
This commit is contained in:
65
app/ios/ScanReceipts/Features/Scan/DocumentCameraView.swift
Normal file
65
app/ios/ScanReceipts/Features/Scan/DocumentCameraView.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user