0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftUI で Write Once, run nearly anywhere

0
Posted at

今回はAIを利用して、WindowsやLinuxでSwiftUI風な記法で、WindowsやLinuxはたまたWebまで、うまく行けばSwiftでWrite once, run anywhere ほい事が実現できるのではないかと実験的なプロジェクトに取り組んでみました。なんとなくいい感じに仕上がってきたとおもうので、この場をかりて発表したいと思います。

背景

普段はiOSとMacのアプリのコードしか書かない私ですが、ある個人的なプロジェクトでLinuxWindowのUIの実装をしたいなと思いました。Linuxは未経験、Windows3.1とかNTの頃で経験が止まっているので、AIと相談しながらコードを書いていました。Linux版など、どう進めればいいかもわからない感じでしたが、GimpやGTKなどをAIに教えてもらいながら、なんとか形になるぐらいの所まで仕上がってきました、

このくらいで欲がでてきます。「なんか、mac/iosのコード一回書いて使い回しができたらなんかよくね?」この後は頭のなかで理性のフューズが切れる音がします。SwiftWindowsUISwiftLinuxUIというリポジトリを作って、SwiftUI ぽいコードがうごくように、ViewやModifierを実装します。表面はSwiftでもその先はCへブリッジして、GTKWin32のAPIに繋ぎます。

この二つが問題を抱えながらでもそれとなく動くようになってくるとふと思います。「おんなじ様なやり方でAndroidでもWebでもできるんでね?」ギアを上げる音がします。先ほど切れたと思ったフューズは高電力対応版に入れ替わっていました。やばい、フューズ飛びません。

二つのプロジェクト、SwiftWindowsUISwiftLinuxUIを一つにするにはどんな名前がある、そこでSwiftCrossUIというプロジェクトがある事に気がつきますが、もはや飛び出した慣性は制御できません。SwiftXUIとか考えましたが、いけていないと思い、AIにSwiftOpenUIという名前を考えてもらいました。「やばい、名前負けしそう」

司令塔はClaude Codeです。立案からコーディング、テストからメインでよく働いてくれます。司令塔とかいいながら、実際はよく使えるADくらいの感じです。本当によく働いてくれます。Claude Codeから大量に上がってくる計画やコードやドキュメントをレビューしてくれるのはCodexです。こいつは鬼レビューアです。ちょっと前までのChatGPTはゆるゆるだったのですが、執筆時点でのCodexXcodeの名前の順番を入れ替えただけのパチモンと思いきや、実際にコードを確認したり、ドキュメントの記述が曖昧だったり、もうええやんと思う様な細かい重箱の隅をちゃんとしつこく指摘してくれます。自分がコード書いてたら、Codexにはあまりレビューしてもらいたくないです。

Geminiは助っ人役です。レビューをやらせると、褒めてしかくれません。Codexの爪の垢を飲ませたいです。おまけに、やるきマンマンで目がギラギラしているのか、レビューしてとお願いすると勝手にコードやドキュメントを修正しちゃう事があるので、「レビューして、レビューだけよ」と念をおさないと、時々やらかしてくれます。

まぁこんな環境で開発しています。環境ついでにいうと、Windows 11 が動くx86PCを持っていないので、開発は全て Mac Mini 2024, Apple M4 Pro, 64GB に UTMの仮想環境、そして、ARM版 Windows 11 Pro、ARM版 Ubuntu の仮想環境で、WindowsWindows上のClaudeUbuntuUbuntu上のClaudeとそれぞれでAgentを動かして作業しています。

動作環境

プラットフォーム バックエンド 動作確認環境
macOS SwiftUI (ネイティブ) macOS 15 Sequoia, Swift 6.2.4
Linux GTK4 Ubuntu 24.04 (ARM64), Swift 6.0+
Windows Win32 + Direct2D Windows 11 Pro (ARM64), Swift 6.0+
Web Wasm + DOM Chrome / Safari, Swift 6.2.4 + Wasm SDK
Android Jetpack Compose 実験段階 (Phase 2)

※ 開発は Mac Mini 2024 (M4 Pro, 64GB) 上で、Linux と Windows は UTM の仮想環境で動作確認しています。

スクリーンショット

同じSwiftコードが各プラットフォームでネイティブ描画されます。Electron やWebViewのラッパーではありません。

Color Studio — 1つのコードベース、4つのプラットフォーム

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

プロジェクト構造

コアライブラリ (Sources/SwiftOpenUI/) はプラットフォーム非依存で、プラットフォーム固有のimportは一切ありません。GTK/Win32/Web のコードは全て Sources/Backend/ に分離されています。

+-----------------------------------------------------+
|  Examples (macOSではSwiftUI, それ以外はSwiftOpenUI)    |
+-----------------------------------------------------+
|  SwiftOpenUI Core                                    |
|  View, State, Layout, Modifiers, Environment         |
+--------------+---------------+---------------+-------+
|  BackendGTK4 |  BackendWin32 |  BackendWeb   | ...   |
|  GTKRenderer |  WinRenderer  |  WebRenderer  |       |
+--------------+---------------+---------------+-------+
|  CGTK/Cairo  |  CWin32/D2D   |  JavaScriptKit|       |
+--------------+---------------+---------------+-------+

各バックエンドはプロトコル拡張 (GTKRenderable, WinRenderable, WebRenderable) でレンダリングを実装します。新しいViewを追加するには、コアに構造体を定義して、各バックエンドにレンダリング拡張を書くだけです。

セットアップ

macOS

Xcode のコマンドラインツールがあれば動きます。

git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
swift build
swift run HelloWorld
swift run ColorMixer

Xcode で開発する場合:

brew install xcodegen
cd apple/Examples && xcodegen generate
open Examples.xcodeproj

Linux (GTK4)

Swift ツールチェーンと GTK4 の開発ライブラリが必要です。

# Swift のインストール (swiftly 推奨)
curl -L https://swift.org/install.sh | bash

# GTK4 開発ライブラリ (Ubuntu / Debian)
sudo apt update
sudo apt install libgtk-4-dev pkg-config

# ビルド & 実行
git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
swift build
swift run HelloWorld
swift run ColorMixer

Windows (Win32)

Swift for Windows と Visual Studio (Windows SDK) が必要です。

  1. swift.org/download から Windows 版 Swift をインストール
  2. Visual Studio 2022 で「C++ によるデスクトップ開発」ワークロードをインストール (Windows SDK を含む)
  3. Developer Command Prompt for VS 2022 を開いて:
git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
swift build
swift run HelloWorld
swift run ColorMixer

Web (Wasm)

Xcode の Swift ではなく、オープンソース版の Swift ツールチェーンが必要です (Xcode版にはWasmバックエンドがないため)。

# 1. swiftly でオープンソース Swift をインストール
curl -L https://swift.org/install.sh | bash
source ~/.swiftly/env.sh

# 2. Wasm SDK をインストール
swift sdk install https://download.swift.org/swift-6.2.4-release/wasm-sdk/swift-6.2.4-RELEASE/swift-6.2.4-RELEASE_wasm.artifactbundle.tar.gz

# 3. Node.js (Vite 開発サーバー用)
brew install node   # macOS の場合

# 4. ビルド & 実行
git clone https://github.com/codelynx/SwiftOpenUI.git
cd SwiftOpenUI
./web/run.sh HelloWorld
# ブラウザで http://localhost:3000 を開く

または、./configure スクリプトで swiftly + Wasm SDK を自動インストールできます (macOS のみ)。

Hello World

全プラットフォーム共通の main.swift です。Viewのコードは完全に同一で、#if で import とエントリーポイントだけを切り替えます。

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

struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack(spacing: 12) {
            Text("Count: \(count)")
                .font(.title)
            Button("Increment") { count += 1 }
        }
        .padding()
    }
}

struct MyApp: App {
    var body: some Scene {
        WindowGroup("My App") {
            ContentView()
        }
    }
}

#if os(macOS)
NSApplication.shared.setActivationPolicy(.regular)
MyApp.main()
#elseif canImport(BackendGTK4)
GTK4Backend().run(MyApp.self)
#elseif canImport(BackendWin32)
Win32Backend().run(MyApp.self)
#elseif canImport(BackendWeb)
WebBackend().run(MyApp.self)
#endif

macOS では実際の SwiftUI としてコンパイルされるため、API の互換性が検証されます。他のプラットフォームでは SwiftOpenUI が同じプロトコル、プロパティラッパー、View 型を提供します。

実装状況

Views (43 / 44 実装済み)

Text, Button, TextField, Toggle, Slider, Image, Color, Spacer, Divider, VStack, HStack, ZStack, Group, ForEach, List, ScrollView, AnyView, EmptyView, NavigationStack, NavigationLink, SecureField, TextEditor, ProgressView, Stepper, Label, Link, TabView, Grid, GridRow, DisclosureGroup, Form, Section, LazyVStack, LazyHStack, LazyVGrid, LazyHGrid, Picker, DatePicker, GeometryReader, Menu, ConfirmationDialog, Canvas, NavigationSplitView

Modifiers (36 / 38 実装済み)

.padding(), .frame(), .foregroundColor(), .background(), .font(), .border(), .opacity(), .offset(), .scaleEffect(), .animation(), .onTapGesture(), .onLongPressGesture(), .onDrag(), .navigationTitle(), .navigationDestination(), .focused(), .cornerRadius(), .shadow(), .rotationEffect(), .overlay(), .sheet(), .alert(), .confirmationDialog(), .onAppear(), .searchable(), .toolbar() など

State 管理

@State, @Binding, @ObservedObject, @StateObject, @EnvironmentObject, @Published, @Environment, @FocusState, @Observable, ObservableObject

プラットフォーム別カバレッジ

カテゴリ SwiftUI 総数 Core GTK4 Win32 Web カバレッジ
Views 44 43 43 43 42 ~98%
Modifiers 38 36 36 36 36 ~95%
State & Data 13 10 10 10 10 ~77%
Navigation 8 7 7 7 7 88%

詳細は Feature Parity Matrix を参照してください。

今後の課題

  • Canvas の抽象化: GTK4 は Cairo、Win32 は Direct2D、Web は Canvas 2D API とそれぞれ異なる描画エンジンを使用。共通の DrawingContext API は動作しますが、プラットフォーム間の完全な互換性には更なる作業が必要です。
  • Android: Phase 2 段階。JNI + Jetpack Compose でレンダリングしますが、共有コードのビルドに制約があります。
  • .onDisappear(): Web バックエンドのリビルドモデル (全DOM再構築) のため、正確なライフサイクル検出が困難です。
  • パフォーマンス最適化: 現在はシンプルな全再構築モデル。差分更新やVirtual DOMは将来課題です。

ライセンス

MIT License

Copyright (c) 2026 Kaz Yoshikawa

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software...

全文は LICENSE を参照してください。

リンク

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?