DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Multi-Platform Architecture (iOS, iPadOS, macOS, visionOS)

SwiftUI promises “write once, run everywhere” —

but real products quickly discover:

  • navigation behaves differently
  • layouts break on macOS
  • input models diverge
  • windows appear unexpectedly
  • keyboard, pointer, and focus change everything

Multi-platform SwiftUI is not about conditional views — it’s about architectural separation.

This post shows how to design one codebase that scales cleanly across:

  • iPhone
  • iPad
  • macOS
  • visionOS

without becoming a pile of #if os() hacks.


🧠 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 }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Inject:

.environment(\.metrics, currentMetrics)
Enter fullscreen mode Exit fullscreen mode

No conditionals in views.


🧭 3. Navigation Architecture per Platform

iPhone

  • single stack
  • push-based

iPad

  • split view
  • column navigation

macOS

  • sidebar + detail
  • multi-window

visionOS

  • spatial stacks
  • scene-based navigation

Do not force one navigation model.

Instead:

struct RootView: View {
    var body: some View {
        PlatformContainer {
            HomeFeature()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Where PlatformContainer switches implementation.


🪟 4. Window & Scene Management

macOS & visionOS are window-first platforms.

Design explicitly:

WindowGroup {
    RootView()
}

WindowGroup(id: "inspector") {
    InspectorView()
}
Enter fullscreen mode Exit fullscreen mode

On iOS, this is ignored.
On macOS, this is essential.

Architecture must assume multiple instances.


🧠 5. Input Model Differences

iOS

  • touch
  • gestures
  • swipe

macOS

  • pointer
  • hover
  • right-click
  • keyboard

visionOS

  • gaze
  • pinch
  • spatial focus

Never write:

.onTapGesture { }
Enter fullscreen mode Exit fullscreen mode

As your only interaction.

Always support:

  • Button
  • keyboard shortcuts
  • focus
  • context menus

⌨️ 6. Keyboard & Command System

macOS & iPad need:

.commands {
    CommandGroup(replacing: .newItem) {
        Button("New Item") {
            create()
        }
        .keyboardShortcut("N")
    }
}
Enter fullscreen mode Exit fullscreen mode

Your architecture should expose actions:

func create()
func delete()
func refresh()
Enter fullscreen mode Exit fullscreen mode

UI wires them.

Logic stays shared.


📐 7. Layout Strategy

Avoid fixed layouts.

Use:

  • adaptive stacks
  • flexible grids
  • dynamic type
  • size classes

Pattern:

if horizontalSizeClass == .compact {
    CompactLayout()
} else {
    RegularLayout()
}
Enter fullscreen mode Exit fullscreen mode

But isolate this in layout containers — not in feature views.


🧪 8. Testing Multi-Platform Correctness

Test:

  • window creation
  • deep linking per platform
  • keyboard navigation
  • focus behavior
  • split view state
  • multi-window restore

Most multi-platform bugs are state bugs, not UI bugs.


❌ 9. Common Anti-Patterns

Avoid:

  • #if os(iOS) inside feature views
  • platform checks inside ViewModels
  • duplicated business logic
  • navigation hacks
  • forced identical UI everywhere

If it feels forced, it 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)
Enter fullscreen mode Exit fullscreen mode

Only the bottom 3 change.


🚀 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

Top comments (0)