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.

Reparked’s map near the Ferry Building in San Francisco, with numbered parking pins and a route card showing a 10-minute drive and 8-minute walk.
Reparked’s route details screen for a selected parking option.
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

From a destination to a ranked list
  1. SourcesSFMTA open data and MapKit results
  2. MergeDe-duplicate by distance and name overlap
  3. DirectionsUp to 12 routes, with limiter, retry, and cache
  4. 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.