How I Shipped an iOS App in 5 Days with Cursor and Claude
I’m going to tell you the actual workflow. Not the inspirational version. The boring tactical version with the file structures and the prompts and the moments where I got stuck for an hour because of a nonisolated keyword.
If you’re an iOS developer wondering whether AI tools are useful for real work, or a non-iOS-developer wondering whether you can ship an iOS app without learning Swift end-to-end first, this post is for you.
The app is Margin, a press-and-hold note-taking app for podcast listeners. It uses SwiftUI, ActivityKit for the Dynamic Island, AppIntents for the Action Button, Spotify’s Web API with PKCE OAuth, Apple’s on-device Speech framework, and a Widget Extension target. About 25 Swift files. None of that was scaffolding I had before I started. I started at zero on a Sunday.
By Friday I had a working app on my phone that I was using to take notes on actual podcasts. By the next Friday I had it in TestFlight with external testers.
Here’s how.
The tools
Three things were open the whole week:
- Cursor, my editor. I’d used VS Code for years. Cursor is VS Code with Claude built in. I never opened the Anthropic chat UI directly during the build; everything happened inside Cursor.
- Claude (via Cursor), the model doing the actual code generation. I switched between Sonnet for fast iteration and Opus for the harder problems (concurrency, Spotify auth, the Live Activity setup).
- Xcode, for one thing only: building, signing, and running on a real device. I did not type Swift in Xcode. Xcode was the compiler and the deployment tool.
This split, Cursor for code, Xcode for build, is the single most important workflow choice I made. Xcode is a great IDE if you’ve been an iOS engineer for a decade. If you haven’t, it gets in the way. Its autocomplete is slow, its file navigation is clunky, its diffs are bad. Cursor is better at all of these.
The trick is that Cursor doesn’t know how to compile and sign an iOS app, and Xcode doesn’t know how to talk to Claude. So you have both open, and you use each for what it’s good at.
Day 1: scaffolding
The first day was about getting from nothing to a navigable app shell.
I started by writing a single long prompt in Cursor describing what Margin was supposed to be. Not pseudocode. Just product. “Here is what the app does. Here is what the screens are. Here is the design language.” I attached two reference images, a mockup I’d made in HTML the week before, and a screenshot of Things 3 because I wanted the same warmth.
Then I asked Claude to scaffold the project. Specifically I asked for:
- A SwiftUI app with three tabs (Home, Capture, Library)
- An
AppStateobject as the global state, marked@MainActor - A
Models.swiftfile withShow,Episode,Notestructs - A
Theme/Colors.swiftfile with the cream/coral/lavender/lime palette - Mock data so the screens have something to render
Claude produced ~12 files. About 70% of them compiled on the first try. The 30% that didn’t were mostly because Claude was generating Swift 5 code by default and the project was Swift 6 with strict concurrency turned on. I fixed those by hand once and then pasted the fix back to Claude with the instruction “this project uses Swift 6 strict concurrency, prefer @MainActor on observable objects”, and from then on it was fine.
This is the most important lesson of the week: AI tools learn within a session, not between them. Telling Claude your context once at the start saves you from telling it ten times during the session.
By end of day 1: the app launched, had three tabs, mock data showed up in each tab, the design was recognizable. Nothing actually did anything. But it looked like the app I wanted to build.
Day 2: capture and recording
Day 2 was the hard core: implementing press-and-hold to capture audio, transcribing it, saving it.
Three things had to work together.
The gesture. SwiftUI’s standard LongPressGesture doesn’t actually give you the “while holding” state I needed, it just fires after a threshold. I needed start, drag, end. The right pattern turned out to be a DragGesture(minimumDistance: 0) with onChanged and onEnded. Claude knew this immediately; I would not have. This is the kind of thing where AI is most useful, knowing the idiomatic answer to a “how do I do X in SwiftUI” question without you having to read three Stack Overflow threads.
The recording. I used AVAudioRecorder with an AVAudioSession configured for .playAndRecord. The first version worked once and then failed silently every other time. The actual issue was that I was activating the session inside startRecording() and the session was already in a half-state from the previous run. The fix was to activate the session once at app launch and never deactivate it. Claude got me to this answer in about three iterations, I’d describe the symptom, it would propose a fix, I’d test, report what happened, it would refine.
The transcription. I used Apple’s SFSpeechRecognizer with requiresOnDeviceRecognition = true. Easy in principle. In practice, for very short recordings (<2 seconds), the recognizer would sometimes hang. The fix was to wrap the recognition call in a withThrowingTaskGroup with a 30-second timeout. Claude wrote the timeout wrapper for me in about two minutes; writing it from scratch would have taken me an hour.
This is where AI tools shine. Not at deciding what the app should do, but at typing the boilerplate for the things I’d already decided.
By end of day 2: I could press a button on my phone, speak, release, and see a transcribed note appear in the Library tab.
Day 3: Spotify
Day 3 was Spotify integration. This was the hardest day.
Spotify uses PKCE OAuth, the version of OAuth for native apps where you can’t safely store a client secret. The flow is:
- Generate a code verifier and code challenge
- Open Safari with an authorize URL
- Spotify redirects back to your app via a custom URL scheme
- You exchange the code + verifier for an access token
- You refresh the token periodically
Claude wrote about 80% of the auth code correctly. The 20% I had to debug was the URL scheme registration in Info.plist (which Claude couldn’t see) and the actual callback handling, which is a thing Claude knew the pattern for but kept getting subtly wrong because every app has its own scheme.
The breakthrough on day 3 was that I learned to paste error messages directly into Claude. Xcode’s console gave me an error like “Spotify authorize callback failed: malformed redirect URI”, and I copy-pasted the entire stack trace into Cursor. Claude immediately diagnosed it as the URL scheme not matching the one I’d registered in App Connect. Two clicks in Xcode and it worked.
By end of day 3: I could tap “Connect Spotify” in Margin, log in to my Spotify account in Safari, return to Margin, and see the currently-playing episode appear at the top of the Home screen.
Day 4: Live Activity, AppIntents, polish
Day 4 was the extras that make the app feel real.
Live Activity, the thing in the Dynamic Island and on the lock screen. This required a separate Widget Extension target, a shared MarginCaptureAttributes struct, and ActivityKit code. Three new files. I copied them from Apple’s sample code, then asked Claude to adapt them for my data model. Claude did this in one shot.
AppIntents / Action Button, so users with an iPhone 15 Pro or later can map the side Action Button to “capture a Margin note.” This required a CaptureNoteIntent struct conforming to AppIntent, plus an AppShortcuts provider. Claude wrote both correctly; the only thing that broke was the user-visible phrase, which had to include \(.applicationName), Apple validates this. Took two iterations to fix.
Haptics, adding UIImpactFeedbackGenerator calls at the right moments. Pure boilerplate. Claude produced it instantly.
Settings screen, custom fonts, app icon, these are the things that take 80% of the time on a normal iOS project and probably 20% with AI. I gave Claude my color palette, the fonts I wanted (Space Grotesk, JetBrains Mono), and one screenshot of a reference settings screen. It produced a clean settings screen on the first try.
By end of day 4: the app was the app I’d been imagining. I started using it on my real commute.
Day 5: build, sign, archive
Day 5 was supposed to be the easy day. It was not.
This is the part where Cursor and Claude don’t help, because the entire bottleneck is Xcode and App Store Connect.
What I had to do:
- Generate certificates and provisioning profiles in App Store Connect
- Configure bundle IDs for both the main app and the widget extension
- Configure URL schemes for Spotify callback
- Configure entitlements for background audio
- Archive the app in Xcode (Product → Archive)
- Upload to App Store Connect
- Wait for processing
- Add to TestFlight Internal Testing
- Test internally
- Add External Beta testers and submit for App Review
I tried Xcode Cloud first. It got stuck at 46%. I switched to local archive and it just worked.
The biggest lesson from day 5: AI helps you write code, not ship code. The infrastructure of getting an app onto a phone that isn’t yours involves a dozen different web UIs at Apple, each with its own gotchas, and none of them are made better by a chat model. If you’re new to iOS, budget an entire day for this step, not because it’s hard but because there are a hundred small things that can go wrong.
What AI was great at
In order of frequency, the things Claude did extremely well:
- Boilerplate Swift. Anywhere I’d already decided what I wanted, Claude typed it faster than I could. SwiftUI views, data models, view modifiers, simple animations.
- Idiomatic answers to “how do I do X in SwiftUI.” I never once looked up Swift documentation. Claude just knew.
- Debugging. Paste an error message, get a diagnosis. This was the highest-leverage interaction of the week.
- Translating between paradigms. When I’d describe a behavior in plain English (“I want the mic button to pulse coral when recording”), Claude could produce the SwiftUI code without me having to choose between
.scaleEffectand.opacityand.animation.
What AI was useless at
In order of pain caused, the things Claude was bad at:
- Design taste. Claude would happily produce a fine-looking app. It would never produce Margin. The cream backgrounds, the specific shade of coral, the mono timestamps, the lowercase “rec” pill, these came from me. Asking Claude to “make it look good” produces a confident, generic, slightly Material-Design-flavored result. Design judgment doesn’t outsource.
- Architecture decisions. Should this be a struct or a class? Should this be
@StateObjector@EnvironmentObject? Claude has opinions but they’re not always right for your specific case, and when they’re wrong, the consequences propagate through ten files. I had to be the architect. - Apple’s release pipeline. Xcode Cloud quirks, provisioning profile mysteries, App Store Connect’s UI, Claude has read about these but doesn’t know them. You will be on the Apple developer forums.
- Knowing when you’re going down a bad path. Once on day 3, I asked Claude to add a real Spotify Web Playback SDK integration before I’d verified that PKCE OAuth was working. It happily produced 200 lines of code that I had to delete the next day. AI doesn’t stop you from doing the wrong thing in the wrong order.
The actual prompts that worked
People always ask about the prompts. Here are the ones I used most:
- “This project uses Swift 6 strict concurrency. Prefer
@MainActoron observable objects. Usenonisolatedfor objectWillChange publishers if needed.”, start of every session - “Here’s the error from Xcode: [paste]. What’s the fix?”, every time something broke
- “Look at the design language in the attached screenshot. Match the spacing, font weights, and color use.”, when generating views
- “Don’t write tests. Write the feature.”, Claude defaults to writing tests, which I didn’t want this early
- “This is for an indie app. Avoid enterprise patterns like dependency injection containers.”, when Claude over-engineered
That last one was important. Claude has read a lot of Robert C. Martin and tends to over-engineer when you let it. Tell it the app is small and indie, and it adjusts.
Would I do it again
Yes. With caveats.
I built an app in two weeks (with the second week being the App Store dance) that would have taken me two months without AI. That’s a 4x speedup, not 100x. The reason it’s not 100x is that the hard parts of building an app, the design decisions, the product calls, the patience to ship, are still all on you.
But the boilerplate compression is real. Every interaction I had with Claude saved me 5-30 minutes of typing or looking things up. Compound that over a thousand interactions and you have a real app you wouldn’t otherwise have shipped.
If you’ve been thinking about building something and the only thing stopping you is that you don’t know the platform, try it. The barrier is genuinely lower than it was a year ago.
Margin is on the App Store soon. It’s the app I built this way. If you want to see what comes out of two weeks of one person, Cursor, Claude, and a lot of black coffee, go look.
Selinay
Note taking for podcasts.
Press and hold to capture a thought. Margin auto-pauses Spotify, transcribes your voice, and pins your note to the exact moment in the episode that triggered it.
Get early access →