GateSpeed is an iOS app that measures how fast a rider travels through a five‑post slalom course using nothing but the iPhone's rear camera — no beams, sensors, or wearables. Prop the phone up facing the course and it detects the rider crossing each post, times the run, computes the average speed, calls the result out loud, and saves an annotated video of the ride.
Set the phone down in landscape with the whole course in frame. GateSpeed draws five vertical posts at fixed positions across the view (at 10%, 30%, 50%, 70%, and 90% of the width).
- Calibrate — On launch (and whenever you tap Recalibrate), the app waits until no rider is in view. Keep the course clear until the status changes from Calibrating… to Ready.
- Start — A run begins when the rider enters an outer post (post 1 or post 5), which also sets the direction (left‑to‑right or right‑to‑left).
- Measure — The rider must cross the posts in order. The status pill shows the next expected post and the elapsed time.
- Result — When the rider clears the final post, GateSpeed computes the speed over the 28 m course and shows it in km/h with a PASS/FAIL verdict (pass ≥ 30 km/h), then announces it aloud (e.g. "31 kilometres per hour. Pass.").
- Re‑arm — Once the result has been announced and the course is clear again, the app automatically re‑arms for the next run.
A run is flagged Invalid (and discarded) if it starts at an inner post, starts ambiguously (two outer posts at once), skips a post or goes out of order, or takes longer than 10 seconds.
Detection uses the Vision framework's on‑device human‑rectangle detector
(VNDetectHumanRectanglesRequest) — no network, no custom model. Each camera
frame is scanned for people; the rider's bounding box is mapped into the visible
(preview‑cropped) region and its horizontal centre is tracked frame to frame. A
post counts as "crossed" the moment the tracked centre passes its position, and
the crossing time is interpolated between detection samples, so timing stays
accurate even when frames are dropped. Tiny detections (bystanders far away) are
ignored, brief detection gaps are bridged, and implausible jumps restart tracking
so a second person can't hijack a run. When several people are visible, the one
closest to the tracked rider wins (before tracking starts, the most prominent
one).
The bottom‑left corner keeps a running tally for the session:
- Runs — number of valid (completed) runs
- Pass — how many of those met the passing speed
- Avg — average speed across all valid runs
Tap Reset to clear the tally. The same scoreboard is burned into every exported video.
Every valid run is saved to your Photos library as a clip with the gate posts, the speed/verdict panel, and the session scoreboard burned into the footage — the posts light up in turn as the rider passes them, and the result appears as they cross the line. Recording keeps rolling for a few seconds past the finish so the result plays over real footage instead of a frozen frame. Invalid runs are recorded but discarded.
- iPhone running iOS 26.2 or later
- Rear camera (ultra‑wide is preferred; the wide camera is used as a fallback)
- Landscape orientation
- Camera permission (required) and Photos "Add" permission (to save ride videos)
Open the project in Xcode and run the GateSpeed scheme on a physical device (the camera is required):
GateSpeed/GateSpeed.xcodeproj
Or from the command line:
xcodebuild build \
-project GateSpeed/GateSpeed.xcodeproj \
-scheme GateSpeed \
-destination 'generic/platform=iOS'xcodebuild test \
-project GateSpeed/GateSpeed.xcodeproj \
-scheme GateSpeed \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro'The unit tests cover the timing state machine, speed math, rider tracking and
crossing interpolation, Vision coordinate mapping, and session statistics. One test — the burned‑in video overlay
(RideVideoOverlayComposerTests) — is device‑only: the Core Animation video
compositor crashes in the iOS Simulator, so it is skipped there and must be run on
a real device.
| File | Responsibility |
|---|---|
GateSpeedApp.swift |
App entry point |
ContentView.swift |
SwiftUI UI — camera preview, gate overlay, status/result panels, session scoreboard |
CameraCaptureService.swift |
Capture‑session setup; wires detection → state machine → recording |
CameraPreview.swift |
AVCaptureVideoPreviewLayer bridge; reports rotation and viewport geometry |
RiderDetector.swift |
Vision human detection, rider tracking, post‑crossing timing |
GateModels.swift |
Course layout, run state machine, results, session statistics |
RideVideoRecorder.swift |
Records the run and saves it to Photos |
RideVideoOverlayComposer.swift |
Burns the posts / result / scoreboard into the exported clip |
SpeechAnnouncer.swift |
Spoken result announcements (British English) |
Course parameters (post spacing, the 28 m distance, the 30 km/h pass threshold, and
the 10 s timeout) live in GateLayout and GateRunStateMachine in
GateModels.swift.
MIT © 2026 Lukasz