Ask an AI coding agent to build a settings screen in SwiftUI, and it'll hand you something that compiles everywhere Apple ships a screen — iPhone, iPad, Mac, even Vision Pro. That's not the hard part anymore. Getting an LLM to produce SwiftUI that builds across Apple platforms is almost table stakes in 2026.
Getting it to look like it belongs on each of those platforms is a completely different problem, and it's one that doesn't show up as a red squiggle anywhere.
The bug that isn't a bug
Run that agent-written settings screen on iPad and you get a phone layout stretched into a wider window. Run it on Mac and there's no sidebar, no menu-bar affordances, nothing that reads as "a Mac app" instead of "an iPhone app someone recompiled." The code is correct. It compiles, it runs, nothing crashes. It's just wrong in a way that only shows up if a human actually opens it on the other two devices — which an agent, working from a single mental model of "the app," has no particular reason to do unless you force it to.
This is the part of "AI writes your UI now" that gets glossed over. Hallucinated APIs are a solved-enough problem — point an agent at a real component library with real signatures, and it stops guessing at method names. Platform idiom is a different kind of problem, because there's no API to look up. Nobody ships a function called shouldThisBeASidebarOrATabBar(). It's a judgment call based on Apple's Human Interface Guidelines, and an LLM only applies that judgment if something in its context explicitly encodes it — otherwise it defaults to whatever idiom it generated first and just reuses it everywhere.
What "handle it internally" actually requires
I ran into this concretely working on DesignFoundation (MIT) and its paid companion DesignFoundationPro — a SwiftUI design system built, among other things, to be handed to an AI agent as a reference instead of left for it to improvise against. Both ship an instruction stating the constraint plainly:
You do not need
#if os(macOS)or#if os(iOS)to use any component. Platform differences are handled internally.
That's a real architectural decision, not a marketing line — it means the idiom switch has to live inside the component, not in app code the agent writes. The cleanest example is one adaptive shell:
// Same call. Tabs on iPhone, sidebar on iPad and Mac.
DFAdaptiveShell(
tabs: [DFAdaptiveTab(id: "home", label: "Home", icon: "house")],
selectedTabID: $selection
) { tabID in
DetailView(id: tabID)
}
One call site. The agent never writes a branch for "iPhone version" vs. "iPad/Mac version," because that branch doesn't exist in its output — it's resolved at runtime inside the component, using a DFPlatformContext the theme modifiers inject automatically (idiom, size class, whether Liquid Glass is available). For a human developer, that's a nice abstraction. For an agent with zero HIG training baked into "did I get this layout right," it's the difference between possibly producing the wrong idiom and structurally being unable to, because the wrong code path was never generated.
The case that's actually hard: dual-display iPhones
The sharper version of this problem is Apple's dual-display iPhone hardware, where "the same screen" has to behave as one pane folded, two panes side by side, or two panes split across an outer and inner display — and almost nobody's app logic handles that cleanly by hand. Pro's answer is six purpose-built two-pane layouts on one shared container:
DFDuoMasterDetail(hasSelection: false, onBack: { }, placeholderTitle: "Nothing selected") {
MasterView()
} detail: {
DetailView()
}
with the actual pane decision exposed as a pure function you could unit test if you wanted to:
let mode = DFDuoLayoutRules.mode(isSplit: false, width: 390) // .single or .twoPane
let showsMaster = DFDuoLayoutRules.showsMaster(mode: mode, hasSelection: true)
That's the kind of logic I'd expect to see hand-rolled badly across a dozen different codebases — guessed breakpoints, GeometryReader hacks, edge cases around rotation nobody tested — if it weren't centralized somewhere an agent (or a human) can just call into instead of reinventing.
Being honest about where this isn't finished
Not every path here is equally proven. The macOS floating-panel path and the visionOS overlay fallback for the popup system compile but haven't been exercised at runtime yet — that's stated directly in the project's own changelog, not something I'm smoothing over. iOS is the verified path. If Mac or Vision Pro is where your app actually lives, that's the one area worth a manual pass before you trust whatever the agent shipped.
The lesson past this one package
If you're building anything with an AI agent that's meant to run on more than one platform — Apple's three, or web/mobile/desktop for that matter — "does my library have accurate docs" isn't the question that protects you anymore. The question is whether those docs (or the CLAUDE.md/AGENTS.md file the agent is actually reading) encode the judgment calls, not just the function signatures. A signature error gets caught by the compiler. An idiom error gets caught by nothing — it just ships, quietly, as a worse app on whichever platform you didn't personally check.
I posted a deeper, more Swift-specific version of this over on the Swift Forums if you want the full API surface (shell list, DFPlatformContext fields, the Duo layout table). Here I mostly care about the general shape of the problem: compiling across platforms and belonging on each of them are not the same achievement, and right now almost nothing an agent reads tells it the difference.
- Free package: github.com/NerdSnipe-Inc/design-foundation
- Pro: github.com/NerdSnipe-Inc/DesignFoundationPro
Curious if anyone outside the Apple-platform world has hit the same "agent ships one idiom everywhere" issue on their own stack — React Native, Flutter, whatever. Would guess this isn't SwiftUI-specific at all.
Top comments (0)