Why native?
There’s a well-worn path for indie mobile games: build in Unity or a cross-platform framework, ship to iOS and Android simultaneously, maximize your addressable market from day one. It’s pragmatic advice.
I went the other direction. Manu Idle is built with SwiftUI for the UI, Swift for the game logic, and CloudKit for sync and persistence. No game engine. No cross-platform layer. Pure Apple stack.
This wasn’t stubbornness — it was a series of tradeoffs that made sense for this specific game.
The case for SwiftUI in a game
Manu Idle is an idle RPG. The gameplay is menus, numbers, progression bars, and inventory management. There’s no sprite rendering, no physics engine, no real-time animation system. The UI is the game.
For that kind of interface, SwiftUI is remarkably good. Declarative layouts, built-in animation, native platform feel on iPhone, iPad, and Mac from a single codebase. The skill training screen, the equipment panel, the offline summary — these are essentially sophisticated data views, and that’s exactly what SwiftUI was designed for.
A game engine would have given me a canvas and told me to rebuild every UI widget from scratch. SwiftUI gave me the widgets and let me focus on game logic.
CloudKit: the privacy architecture
CloudKit is the reason the privacy story works. Every player’s data lives in their own iCloud private database. I don’t run servers. I don’t have a backend. When your character syncs between your iPhone and your Mac, it goes through Apple’s infrastructure under your Apple ID, encrypted end-to-end.
The practical benefits go beyond privacy. No server costs. No user authentication system to build. No database to maintain. No GDPR compliance headaches beyond Apple’s own framework. For a solo dev, that’s a significant reduction in operational complexity.
The sync problem
CloudKit sync sounds simple until you actually build it. The happy path — save on one device, pull on another — works well. The unhappy paths are where it gets interesting.
What happens when a player trains Smithing on their iPhone at lunch and trains Mining on their iPad at home, then opens the Mac that evening? You have a conflict. Both devices made legitimate progress, and the player expects all of it to count.
My approach: I sync at the level of individual skill states and inventory changes, not entire save files. When conflicts arise, I merge rather than overwrite. If your iPhone says Smithing is level 15 and your iPad says it’s level 14, the answer is 15 — you progressed on one device, and the other should catch up. For inventory, I track transactions rather than snapshots, so items gained on different devices both end up in your bag.
This isn’t perfect. There are edge cases with simultaneous active play on two devices that can produce weird results. But for the typical pattern of “play on phone during the day, check on Mac at night,” it works cleanly.
What’s been painful
SwiftUI on Mac is still rough. The framework works beautifully on iPhone and iPad, but the Mac Catalyst / native Mac experience has gaps. Certain controls look subtly wrong. Window management is fiddly. I’ve spent more time on Mac-specific polish than I expected.
CloudKit’s error handling is also opaque. When a sync fails, the error messages are often generic. Debugging sync issues requires building extensive logging around every CKRecord operation, and even then, some failures only reproduce on specific iCloud account configurations.
And performance: SwiftUI’s redraw cycle can get expensive with large data sets. An inventory screen with hundreds of items needs careful use of LazyVStack and identity management, or scrolling turns to soup. This took more optimization passes than I’d like to admit.
What I’d do the same
The core bet — SwiftUI for an idle game’s UI-heavy interface, CloudKit for privacy-first sync — has paid off. The app feels native because it is native. The privacy architecture is airtight because there’s no server to compromise. Cross-device sync works because Apple handles the hard infrastructure parts.
If I were starting a game that needed real-time rendering or cross-platform distribution, I’d make different choices. But for an idle RPG in the Apple ecosystem, this stack has been the right call.
What I’d do differently
I’d adopt SwiftData earlier. I started with a custom persistence layer and migrated partway through — the migration was painful and I could have saved weeks by starting with Apple’s tooling.
I’d also invest in CloudKit testing infrastructure from day one. Writing integration tests against CloudKit is awkward, and I relied on manual testing for too long. By the time I built proper test tooling, I’d already shipped several sync bugs to alpha testers that could have been caught earlier.