← Back to Blog

I Shipped an iPhone App Without Owning a Mac (It Took Six Tries)

Paul Allington 5 June 2026 8 min read

I have never owned a Mac. Not a MacBook, not a Mac mini, not even an iPod back when those counted as a personality. I'm a Windows-and-Visual-Studio man who has spent the better part of two decades inside the .NET world, and I have been quietly, smugly fine with that. Then I decided my Forest School App needed to be on the iPhone, and my smugness ran straight into the one platform that genuinely does not care how you feel.

What follows is the honest version of "iOS CI is easy now." It is easy now, in roughly the way a marathon is just walking, repeatedly. It works. It also took me six signed builds and an error message I had never seen in my life. If you're a .NET developer eyeing the App Store from the comfort of Windows, this is the post I wish I'd read first.

The Bit I'd Been Avoiding

The app is built with .NET MAUI, which in theory means one codebase targets Android, iOS, Windows and Mac. In practice my project file told a more cowardly story. The target frameworks read net10.0-android;net10.0-windows10.0.19041.0, and the iOS and MacCatalyst lines sat there commented out, with a little note I'd left for myself: "remain off until a Mac build host is available."

That note had been there for weeks. It was load-bearing procrastination. I genuinely believed the next step was a purchase - a Mac mini sulking in the corner, or one of those cloud-Mac rental services that charge by the hour to let you borrow a computer you already resent. Both options annoyed me enough that "later" kept winning.

The thing that finally moved me wasn't a deal on hardware. It was realising I'd never actually checked whether I needed any.

"You Don't Need a Mac" Is Half True

You don't, as it turns out, need to own a Mac. You need to rent ten minutes of one, and GitHub will hand you that for nothing. The whole Apple half of the pipeline runs on a hosted macos-15 runner: it checks out the code, builds the iOS target, signs it, and uploads it to TestFlight using an App Store Connect API key - a little .p8 file - so there's no fragile session login in the middle. I never see the machine. For my level of use, it costs me nothing.

So the headline is true. "You don't need a Mac" is real, and it's brilliant. But here's the honest asterisk that nobody puts on the headline: cloud CI removes the hardware, not the Apple-ness. Everything that makes Apple builds Apple builds - the exact toolchain versions, the certificates, the provisioning, the bundle identifiers - is all still there waiting for you, and it does not negotiate.

NETSDK1100: You Can't Take Windows With You

First failure, almost immediately. NETSDK1100: To build a project targeting Windows on this operating system, set the EnableWindowsTargeting property to true.

Of course. The runner is a Mac. My project still listed a Windows target framework, and a macOS box cannot build a net10.0-windows target no matter how nicely you ask. Obvious in hindsight, invisible in advance. The fix was a one-line commit I labelled, with the weariness of a man learning, "gate the Windows TFM to Windows" - the Windows target only exists when you're actually on Windows, leaving the Mac runner to build the iOS one in peace. This is the sort of thing AI is genuinely good at: it read the error, recognised the pattern, and conditioned the target framework on the operating system before I'd finished sighing.

The Error Nobody Warns You About

Then the one that stopped me dead:

This version of .NET for iOS (26.5.10284) requires Xcode 26.5. The current version of Xcode is 16.4.

I'll be honest with you, I read that three times. The Microsoft.iOS workload I was building against wanted one exact version of Xcode, and the runner had defaulted to a completely different one - 16.4, which is a generation and a half behind what the workload demanded. This is the coupling nobody mentions in the cheerful "just use a hosted runner" tutorials: the .NET-for-iOS workload and Xcode are version-locked to each other, and the runner's default is almost never the one you want.

What made it worse is that the hosted image is generous to the point of confusing. It ships an entire orchard of Xcodes - 16.0, 16.4, the point releases, all the way up through the 26.x line. The build wasn't failing for lack of choice; it was failing because it had quietly chosen wrong. So I added a diagnostic step that did nothing clever - it just listed every Xcode installed on the box - and then pinned the selected one explicitly via MD_APPLE_SDK_ROOT so the build stopped picking for itself. Getting the workload and the toolchain to agree on a single version turned out to be the real fight, and it is a fiddle every single time. There's even an official page whose entire job is telling you which Xcode your workload needs, which tells you everything about how often this bites people.

Six Signed Builds

With the toolchain finally agreeing with itself, I hit the part Apple reserves for newcomers: signing. Bundle identifier registration for com.thecodeguy.forestschool, provisioning, certificates managed through fastlane, and the slow back-and-forth of getting Apple's side to accept that yes, this is my app and yes, I am allowed to ship it.

I tagged the release builds ios-v1, ios-v2, and so on. I would love to tell you it took two. The AI had warned me, reasonably, that "the first signed run often needs one or two iterations." It needed six. ios-v1 through ios-v6, each one a tag, a push, a wait, and a fresh way to be told no. None of them were dramatic. They were the death-by-a-thousand-cuts of mismatched identifiers and certificate plumbing that every iOS developer apparently just accepts as the cost of admission.

And then ios-v6 went green, TestFlight processed it, and a little while later my app was sitting on my own iPhone. I may have made a noise.

Where the AI Actually Earned Its Keep

Here's the thing though. I am not an iOS developer. I don't have the muscle memory for any of this - the certificate dance, the workload-versus-Xcode coupling, the signing vocabulary. A few years ago, this exact task would have meant days of reading Apple documentation written for people who already understand Apple documentation.

What I had instead was Claude Code reading the actual error lines off each failed run and proposing the next move. Not guessing in the abstract - reading NETSDK1100, reading the Xcode version complaint, and knowing what each one meant. That's the shift that matters: the bottleneck stopped being "do I understand Apple's toolchain" and became "how fast can the runner tell me what's wrong."

Which brings me to the genuinely unavoidable tax. AI shrinks the thinking time to almost nothing, but it cannot shrink the wall clock of Apple's pipeline. Every attempt was: push a tag, wait for a macOS runner to spin up and build, wait for TestFlight to process the upload, then install on the phone to check. That loop is minutes at best, and there's no clever prompt that makes it faster. The slow part of iOS CI in 2026 isn't the work. It's the waiting between the work.

What I'd Tell You Before You Start

If you're a Windows .NET developer staring at the App Store and assuming you need to buy Apple hardware: you don't. A hosted runner does the lot, and an AI pair that can read error messages turns the toolchain hazing from a week into an afternoon of irritation. That's a real, genuine unlock, and I'm not going to undersell it.

But go in with your eyes open. "You don't need a Mac" quietly drops the part where you still need exact Xcode versions, real certificates, registered bundle identifiers, and the patience to be told no six times. The hardware was never the hard bit. The Apple-ness was.

And as ever, the human stayed the product manager throughout. I decided the app belonged on the iPhone. I made the calls on the bundle identifier and the signing strategy. The AI did the unglamorous iteration - reading the errors, gating the target framework, pinning the toolchain, trying again. It executed at a speed that made the old way feel like posting letters. But it didn't decide to climb the mountain. I did. It just read the map out loud while I walked.

The Android side has its own special horrors, by the way - a signing-key mismatch that greets you with a SHA-1 fingerprint and a quiet sense of dread. But that's a story for another post. For now, the Forest School App is on the iPhone, I still don't own a Mac, and I remain, against all odds, quietly smug.

Want to talk?

If you're on a similar AI journey or want to discuss what I've learned, get in touch.

Get In Touch

Ready To Get To Work?

I'm ready to get stuck in whenever you are...it all starts with an email

...oh, and tea!

paul@thecodeguy.co.uk