Case study · iOS app · Aug 2026
Reparked
An iOS app that chooses parking by the whole trip, ranking nearby options by drive time, walking time, and community-reported prices.


- Codebase
- ~5.7K lines of Swift, plus tests
- Tests
- 26 XCTest cases, all passing
- Backend
- Supabase, row-level security, SQL functions
- Released
- App Store, August 2026
Overview
Reparked ranks parking options by the whole trip, combining the drive, the walk to your destination, and what other drivers report paying. It then hands navigation to Apple Maps or Google Maps and can start a timer when you arrive.
My role
I built and shipped it alone: design, iOS app, Supabase backend, and App Store release in August 2026.
How it works
- SourcesSFMTA open data and MapKit results
- MergeDe-duplicate by distance and name overlap
- DirectionsUp to 12 routes, with limiter, retry, and cache
- RankWeighted drive, walk, and community price
Engineering decisions
Ranking honestly on incomplete data
- Problem
- Parking data is sparse: many sources have no price, and some locations are only approximate.
- Approach
- The score is a weighted sum of drive minutes, walk minutes, and a community-reported price when one exists. Missing data is left out rather than guessed, and each result is labeled as a verified entrance or an approximate map point.
- Trade-off
- Prices only count after three reports, so new areas rank on time alone. I preferred a thinner ranking to an invented one.
Working within MapKit’s limits
- Problem
- Real driving and walking routes come from MapKit, which throttles apps that ask for too many.
- Approach
- Directions run in a Swift actor. A limiter allows 3 concurrent requests spaced 0.15 seconds apart, duplicate in-flight requests share one task, and transient failures retry with exponential backoff. Routes are cached for 30 minutes, up to 160 entries, with a stale or straight-line fallback. Ranking caps work at 12 candidates and reserves slots for verified SFMTA entrances, and a test bounds the total at 24 requests.
- Trade-off
- A cap means the best option could fall outside the candidates, so candidates are chosen by distance before routing.
Community prices that clients can’t scrape
- Problem
- Anyone can submit a price, so the data needs abuse limits, and raw reports shouldn’t be readable.
- Approach
- Row-level security makes reports insert-only. A SECURITY DEFINER trigger caps submissions at 10 per hour, and clients read prices only through an RPC that returns medians. With one report, the median is that report’s price, so the app waits for three before using a price in ranking.
- Trade-off
- Sign-in is anonymous to keep the app friction-free, so the per-user cap is a speed bump rather than a hard guarantee, and the three-report minimum is enforced in the app rather than the database.
Starting a timer when you arrive
- Problem
- The timer should start on its own even if the app isn’t open when you pull in.
- Approach
- An 80-meter region around the chosen entrance triggers the parked state, persists the session, schedules an expiry notification, and switches the trip’s Live Activity to a parking timer. Overtime keeps counting on the Lock Screen through local updates, without a push server.
- Trade-off
- It needs Always location permission and has no dwell check, so driving past can trigger it. A manual “I’ve parked” button covers both cases. I’ve tested the logic in unit tests rather than in repeated real drives.
Testing
All 26 XCTest cases pass on the iOS simulator. They cover ranking, route limits, session persistence, overtime, and pricing logic.
What I’d do next
- Add a real published-rates source, since the current providers don’t supply prices.
- Add a dwell check to reduce false arrivals.
- Expand verified data beyond San Francisco.