Live camera source for Scan — and wrestling Swift 6 strict concurrency
Added a live AVFoundation camera as a second Scan source (alongside PhotosPicker), feeding the same OCR→translate pipeline. The interesting part was getting AVFoundation to compile under Swift 6 strict concurrency.
AI version
The next step in the "swap the image source" plan: PhotosPicker → phone camera → (eventually) Meta glasses feed. Each is just a different way to produce image Data; the Vision OCR → /api/translate → display pipeline is identical regardless.
What landed (commit 9272b69)
Camera.swift adds CameraModel (an AVCaptureSession + photo output) and CameraCaptureView (a live preview with a shutter button). Capturing a photo resolves to Data, which flows into the same process(data) that PhotosPicker already used. ScanView now shows two source buttons — Camera (live) and Photo (picker) — so it works both on a real device and in the simulator.
The actual work: Swift 6 strict concurrency
SWIFT_STRICT_CONCURRENCY: complete and AVFoundation don't get along by default. The build failed twice:
AVCaptureSessionis non-Sendable, so capturing it into aTask.detached(to run the blockingstartRunning()off the main actor) is a data-race error.- The capture-photo delegate callback is
nonisolated(arbitrary queue), but it needs to resume a@MainActorcontinuation. - A
@MainActorViewmethod returningsome Viewcouldn't be called fromPhotosPicker's nonisolated label closure.
Fixes (recorded in docs/troubleshooting.md):
- An
@unchecked SendableSessionBoxcarries the session across to the backgroundTask; the delegate maps everything to aSendable Result<Data, CameraError>before hopping back to@MainActor. - The shared label became a
SourceButtonLabel: Viewstruct — constructed, not a MainActor method call — so nonisolated closures can build it.
After those, BUILD SUCCEEDED with zero concurrency warnings.
Verified — and not
xcodebuild→ BUILD SUCCEEDED (iphonesimulator), Swift 6 strict concurrency clean.- App smoke-launched in the iPhone 17 Pro simulator (no startup regression from the camera code).
- Not verified: the camera itself — the simulator has no camera, so live capture only runs on a real device (my iPhone 16 Pro Max). The Photo source covers the OCR→translate path in the simulator. Translation output still needs valid API keys.
This keeps the architecture honest: the camera is just another Data source. When the Meta glasses path opens up, the MWDAT camera feed slots into the exact same process(data) — no pipeline rewrite.
Commit: 9272b6917d0519f9f00f8ac1d7c5f1f7d9cee17c
Review needed
No human review on this entry yet.