Ship an iOS MVP in 1 Hour
Ship a runnable v1 in 60 minutes: SwiftUI + RevenueCat + PostHog. Core action only, 4-step onboarding, basic paywall, key events instrumented.
๐ Indie App Playbook ยท Part 3 of 7 Series overview: Indie App Playbook
An MVP isn't a rough version of a big product โ it's a small product that validates exactly one core hypothesis.
In one hour you won't ship a full version, but strangers will complete the core action in 30 seconds and you'll see in the data exactly where they drop off.
๐ฏ What you'll ship
- A TestFlight-ready v1 (minimal but complete)
- The core path: launch โ onboarding โ core action โ paywall
- 6 must-have events instrumented
- A real build you can send to your first 50 users
โฑ๏ธ Time blocks
๐ Prerequisites
- Xcode 16+, free Apple Developer account
- Outputs from Part 2: Pick Your App Niche: โค 5 core features + positioning line
- Free RevenueCat + PostHog accounts
Step 1: Scaffold (0โ10 min)
New project
File โ New Project โ iOS โ App
Interface: SwiftUI
Language: Swift
Storage: None (UserDefaults is enough)
Dependencies
File โ Add Package Dependencies:
https://github.com/RevenueCat/purchases-ios
https://github.com/PostHog/posthog-ios
Only these two. Don't add Firebase / Mixpanel / Amplitude โ they slow compile.
Structure
YourApp/
App.swift // @main + register RC/PH
ContentView.swift // Top router
Onboarding/ // 4-step flow
Core/ // Core action screens
Paywall/ // Paywall
Analytics.swift // Event wrapper
Step 2: Core action (10โ25 min)
Mindset: The user completes one key task within 30 seconds of opening. Cut everything else.
5 screens
Max 5 screens:
- Launch / Onboarding entry
- Core action input (the one critical interaction)
- Result page (the moment the user sees value)
- Paywall (appears after value is felt)
- Settings / History (minimal)
SwiftUI skeleton
struct ContentView: View {
@AppStorage("hasOnboarded") var hasOnboarded = false
var body: some View {
if !hasOnboarded {
OnboardingFlow()
} else {
MainTabView()
}
}
}
Don't do
- โ User accounts (Apple ID / device-local is enough for v1)
- โ Backend API (full local in v1, fewer network deps)
- โ Multi-language (do your main market first)
- โ iPad layout (unless iPad is the core scenario)
Step 3: Onboarding + paywall (25โ40 min)
4โ5 step onboarding
Not a product manual โ a funnel from ad promise โ paywall trigger:
- Welcome: restate the ad's core promise ("Never get sunburned again")
- Pain echo: 3 lines of "do you also..." so users nod along
- Value demo: one GIF or 3-step illustration showing how the app solves it
- Permissions (if needed): notifications / location / camera, each with a "we'll only..." explainer
- Paywall: right after, while emotion is primed
Paywall template
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ [One-line value prop] โ
โ Unlock streak tracking. โ
โ Miss a day = back to zero. โ
โ โ
โ โ Unlimited streaks โ
โ โ History export โ
โ โ Widget โ
โ โ
โ [ Yearly $29.99/yr ] โ
โ [ Monthly $4.99/mo ] โ
โ โ
โ Start 7-day free trial โ
โ โ
โ Restore | Terms | Privacy โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Must-have:
- Clear price, clear trial length, clear post-trial billing
- No fake countdowns / fake "limited time" pressure
Restore Purchaseis required (review will reject otherwise)- Close button so users can skip
Step 4: Wire RevenueCat + PostHog (40โ50 min)
RevenueCat (5 min)
Create project at app.revenuecat.com, grab API key, in App.swift:
import RevenueCat
@main
struct YourApp: App {
init() {
Purchases.configure(withAPIKey: "appl_xxxxxx")
}
var body: some Scene { ... }
}
In App Store Connect, create subscription products (yearly + monthly), and create a sandbox tester account.
PostHog (5 min)
import PostHog
let config = PostHogConfig(apiKey: "phc_xxx", host: "https://us.i.posthog.com")
PostHogSDK.shared.setup(config)
6 must-have events
| Event | Fires when |
|---|---|
app_opened | App launch |
onboarding_completed | Finishes onboarding |
core_action_done | User completes one core action |
paywall_shown | Paywall appears |
trial_started | User taps trial |
purchase_completed | Real payment success |
Wrap in Analytics.swift:
enum Event: String {
case appOpened = "app_opened"
case onboardingCompleted = "onboarding_completed"
case coreActionDone = "core_action_done"
case paywallShown = "paywall_shown"
case trialStarted = "trial_started"
case purchaseCompleted = "purchase_completed"
}
func track(_ event: Event, properties: [String: Any]? = nil) {
PostHogSDK.shared.capture(event.rawValue, properties: properties)
}
Rule: instrument everything you might need later. Missing > wrong.
Step 5: TestFlight (50โ60 min)
Checklist
- App name, icon, subtitle filled
- At least 3 screenshots at 6.7"
- Privacy policy URL (Termly / a public Notion page works)
- Support URL (Notion page is fine)
- App Privacy nutrition labels filled (what data, with whom)
- Review notes: test account, subscription test path
- Build version 1.0 (1)
TestFlight, not App Store
Don't submit to App Store directly for v1. Flow:
- Xcode โ Product โ Archive
- Upload to App Store Connect
- In TestFlight tab, enable Internal Testing
- Invite yourself + the first 50 users from Part 1
TestFlight needs no review โ fast iteration. Only submit to App Store after the first 50 users have given feedback.
Checkpoint
End of 60 minutes you should have:
- โ A runnable TestFlight build
- โ Core action completable in 30 seconds
- โ 6 key events visible in PostHog
- โ Paywall completes sandbox subscription
No data = it doesn't exist. Next: build the App Store page so real users can find you.
๐ Further reading
- Full long-form: Indie App Playbook
- Previous: Pick Your App Niche
- RevenueCat docs: https://www.revenuecat.com/docs/
Series navigation
๐ Indie App Playbook ยท Part 3 of 7
- Part 1 โ Validate Your App Idea
- Part 2 โ Pick Your App Niche
- โ Part 3 ยท You are here โ Ship an iOS MVP in 1 Hour
- โญ๏ธ Next:
- ๐ Series overview: Indie App Playbook