SwiftUI Multi-Platform Architecture (iOS, iPadOS, macOS, visionOS)
Source: Dev.to
🧠 The Core Principle
Share behavior.
Specialize presentation.
- Business logic should be 100 % shared.
- UI composition adapts per platform.
🧱 1. Split by Feature, Not by Platform
Bad structure
iOS/
macOS/
Shared/
Good structure
Features/
Home/
Profile/
Settings/
Platform/
iOS/
macOS/
visionOS/
Each feature contains:
- ViewModel
- State
- Business logic
Platform folders contain:
- Wrappers
- Layout adapters
- Platform‑specific views
🧩 2. Platform Abstraction Layer
Create small adapters:
protocol PlatformMetrics {
var sidebarWidth: CGFloat { get }
var toolbarHeight: CGFloat { get }
}
Implement per platform:
struct iOSMetrics: PlatformMetrics {
let sidebarWidth: CGFloat = 0
let toolbarHeight: CGFloat = 44
}
struct macOSMetrics: PlatformMetrics {
let sidebarWidth: CGFloat = 240
let toolbarHeight: CGFloat = 52
}
Inject the appropriate implementation:
.environment(\.metrics, currentMetrics)
No conditionals are needed inside views.
🧭 3. Navigation Architecture per Platform
| Platform | Navigation style |
|---|---|
| iPhone | single stack, push‑based |
| iPad | split view, column navigation |
| macOS | sidebar + detail, multi‑window |
| visionOS | spatial stacks, scene‑based navigation |
Do not force a single navigation model. Instead:
struct RootView: View {
var body: some View {
PlatformContainer {
HomeFeature()
}
}
}
PlatformContainer switches implementation based on the current platform.
🪟 4. Window & Scene Management
macOS & visionOS are window‑first platforms. Design explicitly:
WindowGroup {
RootView()
}
WindowGroup(id: "inspector") {
InspectorView()
}
On iOS these groups are ignored; on macOS they are essential. The architecture must assume multiple instances.
🧠 5. Input Model Differences
| Platform | Input modalities |
|---|---|
| iOS | touch, gestures, swipe |
| macOS | pointer, hover, right‑click, keyboard |
| visionOS | gaze, pinch, spatial focus |
Never rely solely on:
.onTapGesture { … }
Always support:
Button(including keyboard shortcuts)- Focus handling
- Context menus
⌨️ 6. Keyboard & Command System
macOS & iPad need command definitions:
.commands {
CommandGroup(replacing: .newItem) {
Button("New Item") {
create()
}
.keyboardShortcut("N")
}
}
Expose actions from shared logic:
func create()
func delete()
func refresh()
UI layers wire these actions; the underlying logic stays shared.
📐 7. Layout Strategy
Avoid fixed layouts. Use adaptive stacks, flexible grids, dynamic type, and size classes. Example pattern:
if horizontalSizeClass == .compact {
CompactLayout()
} else {
RegularLayout()
}
Isolate this logic in layout containers—not in feature views.
🧪 8. Testing Multi‑Platform Correctness
Test across platforms for:
- Window creation
- Deep linking per platform
- Keyboard navigation & focus behavior
- Split‑view state
- Multi‑window restoration
Most multi‑platform bugs are state‑related rather than UI‑related.
❌ 9. Common Anti‑Patterns
Avoid:
#if os(iOS)inside feature views- Platform checks inside ViewModels
- Duplicated business logic
- Navigation hacks that force a single UI everywhere
If a solution feels forced, it probably is.
🧠 10. Mental Model
Think in layers:
Business Logic (shared)
State Management (shared)
Navigation Model (per platform)
Layout System (per platform)
Input Model (per platform)
Presentation (per platform)
Only the bottom three layers change per platform.
🚀 Final Thoughts
Multi‑platform SwiftUI is not a UI problem — it’s an architecture problem. When designed correctly:
- Features stay reusable
- Code stays clean
- Platforms feel native
- Maintenance stays sane
- visionOS becomes trivial to support