SwiftUI と C++ で GUI アプリを作る
SwiftUI と C++
Mac において GUI の開発に C++ は向いていない。
基本的に Objective-C/Objective-C++ や Swift を用いて開発するのがセオリーだった。
今や Swift だけで簡単に GUI アプリを制作することができる。
でも C++ で GUI 開発をしたい
Swift と Objective-C/Objective-C++ を相互運用することができたため、 C++ ↔ Objective-C++ ↔ Swift という流れで C++ から SwiftUI を巡って呼ぶことはできた。
しかし、 Objective-C/Objective-C++ なんて書きたくない
そういうなか、 C++ ↔ Swift を相互運用することができる機能が Swift に追加されたのである。
要件を以下にまとめる
- Objective-C / Objective-C++ は絶対に書きたくない
- Swift + C++ の構成で SwiftUI を楽に書きたい
- C++ から SwiftUI を叩けるようになりたい
- LSP などの言語支援も適切に
C++ と Swift の相互運用
詳しいことはドキュメントを参照して欲しい。ここでは、具体的な機能の説明やコードの解説は行わない。
C++ と Swift の interop の一番わかりやすい Example は Apple のリポジトリ にあるこのコードである。
基本的にここから進めていく。
LSP の支援
上記の Example のコードをビルドし、適当な LSP を持つエディタで開いても LSP による補完が出てこないと思う。C++(clangd) も swift(sourcekit-lsp) もこのままの構成では適切に LS が構造を把握しきれないからだ。
まずは
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
で compile_commands.json
を生成するコードを書く。
しかしこの compile_commands.json
を sourcekit-lsp は理解しきれないのだ。clangd 側は理解できるが、エラーが出るかもしれない、そういう時は .clangd
に以下の記述が必要すれば治る可能性がある。
CompileFlags:
Add:
- -I/Library/Developer/CommandLineTools/usr/lib/swift
sourcekit-lsp 側の対処はここのフォーラムに書いてあるが、フォーラムの内容をまとめるとcompile_commands.json
に手を加えて sourcekit-lsp が理解できるようにするということらしい。
https://github.com/apple/foundationdb/blob/main/contrib/gen_compile_db.py のコードを用いると良い。
gen_compile_db.py
を適当な場所にコピーをする。その上で
python3 gen_compile_db.py ./build/compile_commands.json -i ./build -b ./build -o compile_commands.json -ninjatool=ninja
を実行すれば compile_commands.json
が生成され、 swift の補完が正しく働くようになる。
SwiftUI の設定
上のリポジトリの lib/fibonacci/fibonacci.swift
を以下のように書き換える。
import Fibonacci
import AppKit
import SwiftUI
public func fibonacciSwift(_ x: CInt) -> CInt {
print("x [swift]: \(x)")
if x <= 1 {
return 1
}
return fibonacci_cpp(x - 1) + fibonacci_cpp(x - 2)
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World")
}.padding()
}
}
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
public func run() {
MyApp.main()
}
そして src/fibonacci.cpp
を
#include "fibonacci/fibonacci-swift.h"
#include <iostream>
int main(int argc, char **argv) {
std::cout << SwiftFibonacci::fibonacciSwift(5) << std::endl;
SwiftFibonacci::run();
return 0;
}
のようにすれば ./build/src/fibonacci_cpp
から無事 GUI アプリが立ち上がるはずだ。
Discussion