Building FotoCraft: real-time composition guidance on iOS
FotoCraft started as a simple frustration: I kept taking photos I thought were well-composed, only to look at them later and realize the framing was off. The “rule of thirds” is easy to read about and hard to feel in the moment. So I built an app that gives you feedback while you’re still holding the camera.
This is a quick tour of the architecture and the decisions that mattered most.
The camera overlay loop
The core experience is a live preview with overlays — rule-of-thirds grid,
leading-line hints, a horizon-level indicator — drawn on top of an
AVCaptureSession. SwiftUI alone can’t own the camera, so the capture pipeline
lives in a UIViewControllerRepresentable wrapping AVCaptureVideoPreviewLayer,
and the SwiftUI layer floats above it.
The tricky part was keeping the overlay reactive at 60fps without blocking the main thread. I pipe preview metadata through a Combine subject on a background queue and only marshal to the main actor for view updates, debounced to ~15fps. Anything more was wasted work; the eye can’t read the grid faster than that.
Haptics as feedback, not gimmick
UIImpactFeedbackGenerator fires a soft tick when the horizon levels out and a
crisper one when the subject crosses a thirds intersection. It sounds minor, but
haptics turned out to be the feature people mention first. The lesson: a tiny
amount of well-timed tactile feedback does more for “feel” than any animation.
AI scoring with OpenAI Vision
After capture, the photo gets sent to OpenAI Vision for a composition score and short critique. The interesting design problem wasn’t the API call — it was making the latency feel acceptable. Two things helped:
- Stream the critique so text appears word by word instead of all at once after a long pause.
- Score locally in parallel using a lightweight on-device heuristic (edge density + center bias) so there’s something on screen within 200ms, and the network score refines it when it arrives.
StoreKit 2 and the curriculum
The 45-lesson curriculum is gated behind a single subscription, configured with
StoreKit 2’s async/await API. Transaction verification is dramatically nicer
than the old StoreKit 1 delegate soup — Transaction.updates as an async
sequence means handling renewals and family-sharing is just a for await loop.
What I’d do differently
The capture/overlay split was right, but I underestimated how much edge-case work permissioning and lifecycle (backgrounding mid-capture, interrupted sessions) would eat. Next time I’d build the session-management state machine before any UI.
FotoCraft is live on the App Store. I’m still adding lessons — and still learning to see frames myself.