Inspiration

I have been to 23 countries and I could not tell you how many cities. The photos are all on my phone, each one stamped with where and when it was taken, and none of that ever turns into anything. Existing travel trackers ask you to type it all back in by hand, which is why most people give up after their first trip.

So the question I started with was narrow: if the record of where you have been is already sitting in your camera roll, why are you retyping it?

What it does

Farhop turns your photo library into a filled-in world map.

You point it at your photos and it reads only the date and location metadata attached to each one, groups them into trips and places, and shows you the result before anything is saved. The photographs themselves never leave the phone. Nothing is uploaded, and there is no photo storage on the server at all.

From there:

  • Your world map fills in as you travel, countries first
  • A check-in takes about twenty seconds: pick a place, a city or a country, add a photo, stamp it
  • Passport keeps the count: countries, cities, places, days, first and last visit
  • Farhop Rewind replays a stretch of your travel as full-screen stories, and each slide exports as a 1080x1920 video you can share
  • Share cards are 9:16 images built from your own stats

Everything works with no signal. The app writes to a local database first and syncs when there is a network, so a flight or a foreign SIM changes nothing about whether you can log a place.

Free covers countries and unlimited manual check-ins. Pro adds the city density map, the spinnable globe, per-country region breakdowns, photo import past three countries, photo selection on share cards, and Rewind.

How I built it

Native SwiftUI, iOS 18 and up, Swift 6 with strict concurrency on. The code is one app target plus a local Swift package split into eleven modules, so the map renderer, the import clusterer, the entitlement layer and the data layer can each be tested without a simulator. Close to 900 tests run in about seven seconds.

Local first, not offline as an afterthought. Every write goes to GRDB on the device and into an outbox table in the same transaction. A sync engine drains that outbox to Supabase. If the network is gone, the app does not behave differently. Sign-in restores your trips from the account, so a new phone rebuilds itself.

Privacy as an architecture decision. Coordinates are rounded to two decimal places, about 1.1 km, before they are written anywhere, including on the device. Photos are stripped of EXIF and kept locally. Row level security on Postgres denies everything by default, and the policies are covered by 87 pgTAP tests that run in CI.

Offline geography. Country shapes come from Natural Earth, and 34,144 cities from GeoNames ship inside the app, so city lookup and search answer instantly with no network and no rate limit. Apple's geocoder answers about fifty requests a minute, which is nothing when an import has 500 places to name.

RevenueCat runs the money. The SDK is the only thing the app asks about entitlements, through one pro entitlement. Seven separate paywall placements are wired through Offerings and Placements, so each locked surface can be sold differently without shipping a build. Customer Center handles manage, cancel and restore. Four subscriber attributes (countries, cities, trips, whether the user has imported) go up so offers can be targeted at people who have actually used the app. A webhook writes every purchase event into Postgres as a durable record, with HMAC verification and replay protection.

Paywalls that show the thing. Instead of a list of bullet points, tapping a locked feature renders the real feature behind a half-height paywall, heavily blurred, or shows a labelled sample for features that are not a screen. It sells what you are about to get without handing you a screenshot of it.

Challenges I ran into

Importing a real camera roll. My own library is 539 places across 27 trips. The first version fetched full-resolution originals over iCloud one at a time and took tens of minutes. It now reads display-sized renders offline, records what it could not fetch, and downloads those three at a time in the background with a timeout, so one stalled transfer cannot hold up the queue.

Apple rejected the first build. Guideline 2.1(a): the app is Sign in with Apple only, and review asked for a username and password. The fix was a dedicated demo account for App Review, backed by a Postgres auth hook that refuses email sign-ups for everyone except that one account, so everyone else still signs in with Apple.

A pre-launch audit found real holes. I ran a structured review across data integrity, security, legal compliance, robustness and monetization before submitting. It turned up a background task handler that crashed on every launch and gaps in sign-out and row level security. All of them were fixed before release, each with a test that fails if it comes back.

Accomplishments that I am proud of

The app does what it says about photos. That claim is in the App Store description, in the privacy policy, and in the code: there is no photo upload path, and no photo table on the server.

Rewind renders video on the device with no server and no third party, frame by frame through ImageRenderer into AVAssetWriter, so a recap of your year becomes a shareable MP4 without anything leaving the phone.

What I learned

Shipping is mostly the unglamorous half. The feature work was weeks; getting through App Review, tax forms, trader status, a privacy manifest that matches what the code actually sends, and a purchase flow that survives a refund took about as long.

Also: test the money. The purchase path was the last thing I verified end to end, and it was the only part of the app where a mistake costs revenue rather than a bug report.

What's next for Farhop

Friends and a shared scoreboard, which the data model already supports and the UI does not expose yet. Trip pages you can send to someone who does not have the app. Web funnels for the plans.

Built With

Share this project:

Updates

Submission history