Inspiration

It started with a colleague and friend who makes apps. Seeing those apps made me want to build one myself, so I dug out my old notes of project ideas. The one I picked was a problem of my own: thousands of photos I kept meaning to clean up and never did, because doing it in Photos means scrolling a grid and ticking tiny circles. It became my first app.

I wanted cleaning up to feel like going through a stack of prints: one photo at a time, a quick decision, and a pile that's actually finished at the end.

What it does

Limina shows your photos as a deck of cards. Swipe right to keep, left to delete, 15 at a time, so a round is always short enough to finish. Nothing is deleted until you confirm, and it never asks about the same photo twice. It works with VoiceOver and speaks 16 languages, and in Arabic and Hebrew even the swipe is mirrored.

The free version gives you 45 photos a day, with ads from the third launch. Pro removes the limit and the ads, and brings Infinite mode: rounds chain together, so you keep swiping and confirm the deletions when you decide to stop. It also unlocks videos, Live Photos, similar shots with the best one starred, and USB drives or SSDs.

The idea is an app that's as pleasant to use as possible and still makes money. So the first launch has no wall of pop-ups: you allow access to your photos, pick what to clean and start swiping. The paywall never opens on its own, not even at the daily limit: there, tapping a padlock opens a short paywall with both plans, and the crown at the top opens the full one. The ads are a bit of friction on purpose, to nudge people toward Pro, but they fit in: after the 15 photos of a round comes an ad card you swipe away like any photo, and there's one more ad in the round's summary.

That balance isn't set in stone: if it doesn't work, the next step is more friction, or something that converts better, like an onboarding. But this gentler approach gets a fair chance first.

Award categories

These are the awards Limina is going for, and why. Each one has its own answer in the submission.

  • RevenueCat Design Award: a deck where the card takes the shape of the photo, and a Home that fits on one screen. In the video from 0:08.
  • Catvertising: two native ads tracked by RevenueCat Ads, held back with their consent prompts until the third launch. In the video at 0:51.
  • HAMM: three revenue streams measured in one RevenueCat project, a free tier that never expires, and a paywall that never opens on its own. In the video at 1:03.
  • RevenueCat Peace Prize: VoiceOver that describes each photo (in the video at 1:29), a swipe that's mirrored in Arabic and Hebrew (at 1:42), support for every iPhone since 2018, and prices that follow each country's purchasing power.
  • Influencer Award (Productivity): one job done fast, with one photo at a time and one decision per swipe.
  • Grand Prize: two updates in the two weeks since launch, and a reworked App Store listing for 1.2.

How we built it

Swift and SwiftUI, with the core logic in Swift packages whose tests run without a simulator. SwiftData remembers every decision, and Vision scores and describes photos on the device. RevenueCat handles the subscription and the lifetime purchase, and RevenueCat Ads, through its AdMob adapter, tracks the ad revenue, so it all lands in one dashboard.

Being a first app, Limina is backed by a lot of tests: UI tests run Apple's accessibility audit on every step of a cleaning session, in left-to-right and right-to-left layouts, and they also capture the App Store screenshots in all 16 languages.

Challenges we ran into

  • Apple rejected 1.1 because the reviewer couldn't find the tracking prompt. It only appears on the third launch, when ads start, and a review doesn't usually launch an app three times. Moving it to the first launch would have been the easy fix. Instead, the review notes explained it, and 1.1 was approved.
  • Infinite mode without seams. Loading a thousand photos up front made no sense, but waiting until the last card to fetch the next round left a stutter at every boundary. Now the next 15 are fetched when four cards are left, only the cards near the top of the deck get their images loaded, and undo still steps back across the seam into the round before. It feels like one endless deck.
  • iCloud photos on an old test phone. The phone had years of history, with many photos stored only in iCloud and some stuck halfway through deletion. Some cards showed iOS's broken-image icon, and others kept spinning even after the app was reopened. Finding which photos were behind each problem, and why, took a lot of digging. Now each image is checked before it's shown, a failed load is retried and then requested another way, and a request that never answers is cancelled instead of waiting forever. A card that's waiting on iCloud says so. Sizes were wrong too. Some never got past their first estimate, like a 50-minute screen recording of 92 MB that stayed at over 2 GB. Screen recordings now have their own estimate, a stuck measurement is tried again, and a deletion waits for the real sizes before counting the space freed.
  • It crashed on launch on an old iPhone XR (iOS 18.7), before any of Limina's own code ran: a Vision API that Apple documents for iOS 18 isn't all there on that version. Weak-linking fixed it, and only the real device caught it.
  • In Arabic, the card flew the wrong way. The decision was right, but the animation said the opposite, and people trust what they see. The tests used the buttons, so they never noticed. Now the deck only thinks in "keep" and "discard", never left and right.
  • Android with Skip. The Android version uses Skip, so it shares the same Swift and SwiftUI code. The deck was the hard part: getting the drag, the pinch and the animations to feel the same on both platforms, without crashes. And since the code is shared, a fix for Android could break something on iOS. So iOS came first, and the Android version is still in progress.

Accomplishments that we're proud of

  • A first app, live on the App Store, and a big step up from 1.0 to 1.2: a much more mature app, with many more features.
  • A listing in 50 App Store locales, with prices that follow each country's purchasing power.
  • An app that still runs on a 2018 iPhone XR, and already works on the upcoming iPhone Duo (tested in the simulator).
  • Groundwork for future projects: the subscriptions and ads code now lives in a shared package, with the decisions behind it written down, so the next app won't start from zero.

What we learned

  • Shipping an app takes much more than code. Limina was a crash course in Apple's processes, from App Store Connect to App Review, and in ASO, payments, pricing and ads.
  • When you ask matters as much as what you ask: holding ads and consent until the third launch changed how the first session feels.
  • Test on the oldest device you support, and watch for tests that take shortcuts, because they can't see what they skip.
  • Ship small and often. 1.0 came out 40 days after the first commit, and 1.1 and 1.2 followed within two weeks of launch.

What's next for Limina — Swipe Photo Cleaner

Android (the port is underway), new ways to find clutter, like trips and places, and a home-screen widget showing the space you've freed. Also a button to send feedback from inside the app, for example when a translation reads wrong, and keeping the free tier's daily count in the Keychain so reinstalling the app doesn't reset it.

Built With

  • accelerate
  • admob
  • app-store-connect-api
  • app-tracking-transparency
  • avfoundation
  • core-location
  • google-mobile-ads
  • google-ump
  • photokit
  • revenuecat
  • revenuecat-ads
  • skip
  • swift
  • swift-package-manager
  • swift-testing
  • swiftdata
  • swiftlint
  • swiftui
  • uikit
  • vision
  • xcode
  • xcode-cloud
  • xctest
Share this project:

Updates

Submission history