DEV Community

Kaz Yoshikawa
Kaz Yoshikawa

Posted on

SwiftUI for Linux and Windows

Write SwiftUI, Run on Linux and Windows

You love SwiftUI's declarative model. VStack, @State, .padding() — it's the best way to build UI in Swift. But it locks you to Apple platforms.

What if the same code rendered natively on Linux and Windows?

macOS (SwiftUI) Linux (GTK4) Windows (Win32)
macOS Linux Windows

Same Swift source file. Native GTK4 on Linux. Native Win32 + Direct2D on Windows. Real SwiftUI on macOS. No Electron, no webview wrappers.

This is SwiftOpenUI.


What is SwiftOpenUI?

SwiftOpenUI is a re-implementation of the SwiftUI API surface that compiles and renders on non-Apple platforms. You write standard SwiftUI code — Text, VStack, @State, .frame(), .padding() — and it runs on Linux (via GTK4) and Windows (via Win32/Direct2D).

The key design: on macOS, your code imports real SwiftUI. This keeps you honest — if it compiles with Apple's SwiftUI, it compiles with SwiftOpenUI. The view code is identical across all platforms. Only the one-line entry point differs.

It's not a new framework to learn. If you know SwiftUI, you already know SwiftOpenUI.

The architecture is simple:

SwiftOpenUI Core (platform-independent)
    |
    +-- BackendGTK4 (Linux)    -> GTK4 native widgets
    +-- BackendWin32 (Windows) -> HWND + Direct2D rendering
Enter fullscreen mode Exit fullscreen mode

The core library has zero platform imports. All GTK/Win32 code lives in separate backend modules. Views, state management, layout, modifiers, and environment — all shared.


Who is this for?

  • Swift developers who want to ship tools on Linux — server admin panels, developer utilities, data viewers
  • Swift developers who want to ship on Windows — internal tools, build monitors, configuration editors
  • Teams with existing SwiftUI code who want to bring it cross-platform without a rewrite
  • Developers who like Swift's type safety and declarative UI but need to target non-Apple platforms

What you get today

SwiftOpenUI covers a broad surface of the SwiftUI API:

44 views including Text, Button, TextField, Toggle, Slider, Image, Picker, List, ScrollView, NavigationStack, Canvas, Grid, shapes (Circle, Rectangle, RoundedRectangle, Capsule, Ellipse), LinearGradient, RadialGradient, and more.

43+ modifiers including .padding(), .frame(), .foregroundColor(), .background(), .font(), .sheet(), .alert(), .toolbar(), .searchable(), .contextMenu(), .overlay(), .clipShape(), .animation(), withAnimation(), .help(), .resizable(), and more.

Full state management: @State, @Binding, @observable, @Bindable, @ObservedObject, @StateObject, @EnvironmentObject, @Published, @Environment, @FocusState, @FocusedValue.

App structure: WindowGroup, Window, Commands with native menu bar on both GTK4 and Win32.

Here's a concrete example. This SwiftUI view compiles and renders identically on all three platforms:

struct SettingsRow: View {
    let label: String
    let value: String

    var body: some View {
        HStack {
            Text(label)
            Spacer()
            Text(value)
                .foregroundColor(.gray)
                .lineLimit(1)
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 10)
    }
}
Enter fullscreen mode Exit fullscreen mode

Nothing platform-specific. Standard SwiftUI.


Setting up your development environment

macOS

You probably already have everything you need.

xcode-select --install
git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
swift run HelloWorld
Enter fullscreen mode Exit fullscreen mode

On macOS, examples use real SwiftUI — this is your reference implementation.

Linux (Ubuntu / Debian / Fedora)

Install the Swift toolchain and GTK4 development libraries:

# Install Swift via swiftly (official Swift version manager)
curl -L https://swiftlang.github.io/swiftly/swiftly-install.sh | bash
source ~/.swiftly/env.sh

# Install GTK4 development libraries
sudo apt install libgtk-4-dev          # Ubuntu / Debian
# sudo dnf install gtk4-devel          # Fedora

# Clone and run
git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
swift run HelloWorld
Enter fullscreen mode Exit fullscreen mode

Windows

Install the Swift toolchain and Visual Studio Build Tools:

  1. Download and install the Swift toolchain for Windows (includes the compiler and SPM)
  2. Install Visual Studio Build Tools with the C++ Desktop Development workload (needed for the linker and Windows SDK headers)
  3. Open a Developer Command Prompt or Developer PowerShell
git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
swift run HelloWorld
Enter fullscreen mode Exit fullscreen mode

On all three platforms, HelloWorld shows centered text in a native window — same code, same result.


Your first cross-platform app

Here's the complete HelloWorld source. One file, runs everywhere:

#if os(macOS)
import SwiftUI
import MacExampleSupport
#else
import SwiftOpenUI
#if canImport(BackendGTK4)
import BackendGTK4
#endif
#if canImport(BackendWin32)
import BackendWin32
#endif
#endif

struct HelloWorldApp: App {
    var body: some Scene {
        WindowGroup("Hello World") {
            Text("Hello, SwiftOpenUI!")
                .padding()
        }
    }
}

#if os(macOS)
MacAppLauncher.run(HelloWorldApp.self)
#elseif canImport(BackendGTK4)
GTK4Backend().run(HelloWorldApp.self)
#elseif canImport(BackendWin32)
Win32Backend().run(HelloWorldApp.self)
#endif
Enter fullscreen mode Exit fullscreen mode

The pattern:

  • #if os(macOS) imports real SwiftUI — your parity check
  • #else imports SwiftOpenUI with whichever backend is available
  • The view code (HelloWorldApp, WindowGroup, Text) is identical across all three
  • Only the entry point differs — one line per platform

This is the boilerplate. Every example in the project follows this pattern. Your actual view code is pure SwiftUI.


Building something real

HelloWorld proves the toolchain works. Let's look at something that exercises real layout patterns — the kind of composition that breaks cross-platform frameworks.

The LayoutStress showcase app has five sections, each targeting a different hard case:

Settings rows

The classic label/spacer/value pattern. Full-width rows with right-aligned values and thin dividers:

VStack(alignment: .leading, spacing: 0) {
    Text("GENERAL")
        .font(.caption)
        .foregroundColor(.gray)
        .padding(.horizontal, 16)
        .padding(.vertical, 8)

    settingsRow("Username", value: "kaz.yoshikawa")
    settingsDivider()
    settingsRow("Email", value: "kaz@example.com")
    settingsDivider()
    settingsRow("Language", value: "English")
}

func settingsRow(_ label: String, value: String) -> some View {
    HStack {
        Text(label)
        Spacer()
        Text(value)
            .foregroundColor(.gray)
            .lineLimit(1)
    }
    .padding(.horizontal, 16)
    .padding(.vertical, 10)
}
Enter fullscreen mode Exit fullscreen mode

This tests: HStack with Spacer, VStack(spacing: 0), .lineLimit(1) truncation, sub-pixel divider heights (0.5pt), and full-width expansion via .frame(maxWidth: .infinity).

Dashboard cards

Equal-width flex distribution — two cards side by side, then three:

HStack(spacing: 12) {
    card(title: "CPU", value: "45%", color: .orange)
    card(title: "Memory", value: "2.1 GB", color: .red)
    card(title: "Disk", value: "128 GB", color: .purple)
}

func card(title: String, value: String, color: Color) -> some View {
    VStack(alignment: .leading, spacing: 8) {
        Text(title).font(.caption).foregroundColor(.gray)
        Text(value).font(.title).foregroundColor(color)
    }
    .frame(maxWidth: .infinity, alignment: .leading)
    .padding(12)
    .background(Color(red: 0.15, green: 0.15, blue: 0.15))
}
Enter fullscreen mode Exit fullscreen mode

This tests: .frame(maxWidth: .infinity) inside HStack children (each card must claim an equal share of the available width), nested VStack inside HStack, and background color rendering.

Sidebar / detail split

Fixed-width sidebar with flexible detail pane — the pattern every document-based app uses:

HStack(spacing: 0) {
    VStack(alignment: .leading, spacing: 0) {
        sidebarItem("Inbox", count: "12", selected: true)
        sidebarItem("Sent", count: "3", selected: false)
        sidebarItem("Drafts", count: "1", selected: false)
        sidebarItem("Archive", count: "847", selected: false)
        Spacer()
    }
    .frame(width: 140)

    Color.gray.frame(width: 1)  // divider

    VStack(alignment: .leading, spacing: 12) {
        // detail content
    }
    .frame(maxWidth: .infinity, alignment: .leading)
    .padding(16)
}
.frame(height: 250)
Enter fullscreen mode Exit fullscreen mode

This tests: fixed-width alongside flexible-width, Spacer() pushing content to top, .frame(width:) constraints, and Color as a 1pt divider.

Status bar

The multi-section status bar pattern — this is the exact composition that found real layout bugs during development:

HStack(spacing: 0) {
    HStack(spacing: 6) {
        Color.green.frame(width: 8, height: 8)
        Text("Connected").font(.caption).foregroundColor(.gray)
    }
    .padding(.horizontal, 12)

    Color.gray.frame(width: 1, height: 16)  // divider

    Text("3 files synced")
        .font(.caption)
        .foregroundColor(.gray)
        .frame(maxWidth: .infinity)          // fills center

    Color.gray.frame(width: 1, height: 16)  // divider

    HStack(spacing: 12) {
        Text("45%").font(.caption).foregroundColor(.gray)
        Text("2.1 MB/s").font(.caption).foregroundColor(.gray)
    }
    .padding(.horizontal, 12)
}
.frame(height: 28)
Enter fullscreen mode Exit fullscreen mode

This tests: mixed fixed and flexible sections in HStack, Color as sized dividers, .frame(maxWidth: .infinity) claiming the center, and fixed-height constraint on the entire bar.

The full LayoutStress source is a single main.swift — 300 lines, all standard SwiftUI patterns.


More showcase apps

Calculator (Windows)

Calculator on Windows

A fully functional calculator built with Grid and GridRow — the SwiftUI grid layout system. Each button is a styled view with .background(), .foregroundColor(), and tap gesture handling. The display uses @State to track input, operations, and evaluation. The entire app is one main.swift with ZStack for the dark background and VStack(spacing: 0) for the display + button grid composition.

ColorMixer (Linux)

ColorMixer on Linux

A color picker with RGB sliders, color swatches, and complementary/triadic harmony display. Uses @ObservedObject for the color model, Slider for each channel, ForEach for the swatch grid, and .background(Color(...)) for live color previews. Demonstrates how SwiftUI's reactive state model works identically on every platform — drag a slider on Linux and the color updates the same way it does on macOS.

SimplePaint (Windows)

SimplePaint on Windows

A drawing app with pencil, eraser, line, rectangle, and ellipse tools. Built on Canvas and Path for rendering, .onDrag() for gesture input, and @State arrays for undo/redo history. Shows that SwiftOpenUI handles not just form layouts but also freeform drawing and real-time gesture tracking across platforms.


How we verify layout parity

Cross-platform layout is hard. A view that looks right on macOS can break subtly on GTK4 or Win32 — wrong spacing, misaligned content, collapsed sections. These bugs are invisible to unit tests and only surface in real app layouts.

SwiftOpenUI has an automated layout parity test suite that catches these:

  1. macOS captures the reference. Each test scenario renders a real SwiftUI view, walks the view tree, and saves the positions and sizes as a JSON fixture.

  2. Each platform renders the same view. GTK4 captures GTK widget positions. Win32 captures HWND positions. Same JSON format.

  3. Automated comparison. A shared comparison engine matches leaf nodes (the actual visible content), normalizes coordinates, and reports differences. Structural layout bugs (wrong position, wrong spacing) fail the test. Font-metric differences (expected — different fonts on each platform) are reported as informational.

  4. 50 test scenarios covering stacks, frames, padding, spacers, alignment, and the composition patterns that real apps use.

Current parity: GTK4 passes 49/50 scenarios. Win32 passes 37/50 (remaining are font-metric cascades, not layout bugs).

This is measurable, automated layout parity — not "it looks about right."


Running the examples

SwiftOpenUI ships with 6 showcase apps and 11 parity test apps:

# Showcase
swift run HelloWorld        # Minimal app
swift run Stopwatch         # Timer with start/stop/lap
swift run ColorMixer        # Color picker with sliders and harmony
swift run Calculator        # Grid-based calculator
swift run SimplePaint       # Drawing app with tools and undo
swift run LayoutStress      # Advanced layout composition stress test

# Parity (one per feature category)
swift run ParityViewsBasic
swift run ParityViewsLayout
swift run ParityModifiers
swift run ParityStateData
swift run ParityNavigation
swift run ParityEnvironment
swift run ParityGestures
swift run ParityAnimation
swift run ParityFocus
swift run ParityAppStructure
swift run ParityViewsContainers
Enter fullscreen mode Exit fullscreen mode

All 17 examples compile and run on macOS, Linux, and Windows from the same source.


What's next

Active development is focused on:

  • Adaptive default spacing — macOS SwiftUI uses 0pt between adjacent Text views; SwiftOpenUI currently uses 8pt. Fixing this closes most remaining Win32 layout residuals.
  • Layout priority-based flex distribution — SwiftUI's priority-based algorithm for dividing space among flexible children.
  • Continued parity testing — expanding the 50-scenario suite as new views and modifiers are added.

The project is MIT licensed and open source. If you're interested in cross-platform Swift UI:

Pick a view from the parity matrix, implement the backend rendering, and send a PR. Every contribution moves the framework closer to full SwiftUI coverage.

Top comments (0)