SwiftUI is easy to learn, hard to ship. The tutorials show you @State and @Binding and beautiful animations. Nobody warns you about Core Data migration when you're already live, or that your app will look different on every iOS version.
I've shipped two SwiftUI apps — including CashFlow, a financial tracker. Here's what the tutorials don't cover.
What tutorials teach vs. what actually matters
Tutorials focus on UI. That's the easy part. SwiftUI's declarative syntax makes building interfaces genuinely fun. The hard parts are:
- Data persistence — making sure data survives app updates
- Background states — handling when your app gets suspended or killed
- Navigation edge cases — deep links, state restoration, back button behavior
- Performance on older devices — your iPhone 15 doesn't have the same constraints as an iPhone SE
The Core Data migration nightmare
You shipped version 1.0 with a simple data model. Now you need to add a field to version 1.1. If you don't set up lightweight migration before the first release, your users' data is gone.
// Do this BEFORE your first release, not after
let container = NSPersistentContainer(name: "DataModel")
let description = container.persistentStoreDescriptions.first
description?.setOption(true as NSNumber,
forKey: NSMigratePersistentStoresAutomaticallyOption)
description?.setOption(true as NSNumber,
forKey: NSInferMappingModelAutomaticallyOption)
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Core Data failed: \(error)")
}
}Set this up on day one. You'll thank yourself when version 1.1 ships without data loss.
The 3 things that broke in production
1. ScrollView performance with 500+ items
LazyVStack fixed it, but the transition from VStack wasn't obvious. Use LazyVStack and LazyHStack for any list that could grow beyond 20 items.
2. @StateObject vs @ObservedObject confusion
Use @StateObject when you create the object. Use @ObservedObject when it's passed in. Get this wrong and your view re-creates its data on every render. This caused CashFlow to lose unsaved entries.
3. Dark mode colors that looked fine in the simulator
Colors that look great on your monitor might look terrible on an actual OLED screen. Test on a real device before shipping.
App Store review rejections I've hit
- Missing privacy policy — even if your app is fully local, Apple requires a privacy policy URL
- "Minimum functionality" — your app needs to do more than show a list. Add settings, export, or some interactive feature
- Screenshots not matching device size — you need exact pixel dimensions. Use this tool to check
My actual toolchain
- Xcode — obviously, but I keep it updated to the latest stable
- TestFlight — I beta test every release for at least 3 days before submitting
- fastlane — automates screenshots, builds, and App Store uploads
- SF Symbols — use Apple's icon library instead of custom icons where possible
The one tip that saved me weeks
Write your data layer before your UI. It's tempting to build beautiful screens first, but you'll rewrite them when you realize your data model doesn't support the features you need. I learned this the hard way on CashFlow — built the dashboard, then had to redo it when I realized I needed transaction categories.
Build the skeleton, then the skin. Not the other way around.
Need an iOS app built right?
I ship native iOS apps with SwiftUI — properly, with data persistence, error handling, and App Store approval. Let's talk.