🦅

SwiftUI と C++ で GUI アプリを作る

2024/04/20に公開

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 アプリが立ち上がるはずだ。

GitHubで編集を提案

Discussion