macOSアプリ開発初心者がSwiftUIを使ってアプリを作ってみた

今回SwiftUIを使って初めてmacOS用のアプリを作りに挑戦してみました。

普段はPHP、Python、TypeScript、JavaScript等でWebアプリ開発をしております。
その視点で今後SwiftUIで試してみたい人が検討できるような情報を感想少し残しておこうと思います。

最後にどんなアプリを作ったのかについては少し記載しますが、ざっくりどんなアプリを作ったのかというと、ChatGPTのレスポンスメッセージビューアプリです。
こんな感じで、コマンドで呼び出してレスポンスメッセージを閲覧できるようにするというアプリです。
対話型のChatGPTクライアントはたくさんあるのですが、簡易なメッセージビューアプリというのは見つからなかったのでSwiftUIへの入門として作ってみました。

![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982597/fc9d1d22-27dc-1878-d459-792f07b5e880.gif)

## デスクトップアプリのフレームワーク選定
### そもそもSwiftUIでデスクトップアプリを作る必要があるのか
いきなりSwiftUIについての内容ではなくなってしまうのですが、これについてまずは一番最初に検討しました。
「macOS用のアプリだからSwift/Objective-Cで作るんだよね」となるのはやや早計で、作りたいものによっては代替的なフレームワークも存在します。

代表的なものとして最近は`Tauri`が人気の印象です。コアプロセスはRustで記述する必要がありますが、UI側はJSで作成することができ、フレームワークが非常に開発のしやすいツールを備えています。(ホットリローディングなど)
他にも`Electron`もあります。

これらのフレームワークを使えばクロスプラットフォームのアプリを作ることができます。

#### それでもSwiftUIを使う理由
今回それらのフレームワークと比較してSwiftUIを使いました。
`Tauri`は非常に優秀なフレームワークですが、それでも一部OSネイティヴな制御はできません。
今回やりたかったウィンドウの厳密な制御(ウィンドウがメインウィンドウではなくなった時の制御等)が`Tauri`だとできませんでした。
`Tauri`でもできることは多いですが、細かい制御やmacOS依存の機能を使いたい時にはSwiftUIを使う方が後々問題にはならないと思います。

## SwiftUIを使ってみての感想
### IDE
SwiftUIのIDEについて調べてみましたが、ほぼXcode一択だと思います。
JetBrainsのAppCodeというIDEがあります。現在でもトライアルで使えるため試してみたのですが、非常に良いものでした。
ただ昨年販売を終了したようです。
それによって、デバッグやコード補完などのパワフルさでXcode以外の選択肢はない状態と思われます。

ちなみにXcodeにはVimモードがあります。ただし、Vimの設定ファイルを読み込んだりはできないので、素のVimで使うしかありません。

Webアプリ開発ではIDEがどれかに縛られるということは基本的にないと思いますので、この開発環境的な選択肢の少なさは若干辛い点かもしれません。

### 情報の少なさ
今回SwiftUIでアプリを作ってみて一番難しかったのがこの点です。
私が情報の手に入れ方、調べ方が下手なのかもしれないですが、SwiftUIについては情報が少ない印象でした。
もちろんSwiftUIの基本的な作り方などの情報はありますが、Webアプリの人気フレームワークで検索した時などと比べると情報は少ない印象で、Xcodeのエラーメッセージで挙がった内容についての解決策などを見つけるのは難しかったです。
また、特に少し凝ったことをしようとすると、Swiftが用意しているどういうAPIを使えば良いのかなどの情報が全然出てこず苦労しました。
またmacOSのバージョンによって仕様も変わったりすることがあり、以前のバージョンでは動いていたが今は動かないみたいなパターンも出てきたりします。

Apple公式のAPIリファレンスもなかなか読解が難解です。
例えばこれはアプリのウィンドウのクラスの説明のページですが、どういう関数が使えるのかくらいしか記載されていません。
これだけみても何をどうすればよいのか全然わかりません。
https://developer.apple.com/documentation/appkit/nswindow

MDNやフレームワークごとのdocsのようにサンプルコードが記述されたりはないんですよね...。

今回上記のような理由でかなり困ったのですが、ChatGPT等に問い合わせてたりすると、意外に上手く行く方法を提示してきてくれることが多かったです。
これから初めて挑戦してみる人はChatGPTを使いながらというのは一つ助けになるかもしれません。

### UI
UIシステムは個人的にかなり良いと感じました。
HTML+CSS+JSの環境でUIを検討する時、UIフレームワークの選択肢がかなり多く、またCSSでの装飾の自由自在であるが故に、デザイナーが熟考したようなUIでないとUIがかなりランダムな感じになってしまったりしがちです。
ただSwiftUIではある程度自由に装飾できるものの、用意されたデザインの装飾でことが足りることが多く、それらで装飾しておけばある程度macOSで使われているような見た目に統一されるので、アプリ内でデザインのコンセプトがランダムに混ざり合っているようなことは発生しづらいように思います。

ビューの作成自体もこれだけでテキスト表示のUIが完成するので簡単です。
```swift
struct ContentView: View {
    var body: some View {
        Text("テキスト")
    }
}
```


ただし、サイズ調整がHTML+JSと比べると難しいです。
特にある要素の高さが可変するようなビューを作る時には想像以上に難しかったです。(今でもサイズ調整についてはあまり理解できていません...)
JSを使う場合は、ある要素を取得してその高さを取得して、それを使って別の要素のサイズを変更するというようなこともできますが、SwiftUIではそのようなことはできません。

### キュー (Queue)
SwiftUIでは`GCD(Grand Central Dispatch)`というキューの仕組みがあります。
SwiftUIのアプリ内で行われる処理について適切にキューイングを行う必要があるようです。
これを適切に使わずに全て実行してしまうと、メインスレッドで直列で処理が行われることになってしまい、アプリが動かなくなったり落ちたりしてしまいます。
UIの更新に関してはメインスレッドで行わないといけないというような決まりがありますが、その他のタスクはメインスレッド以外で並列で処理するなど、キュー管理の理解が必要になります。
これは`Tauri`などのようなフレームワークでアプリ作成する場合とは違って考慮が必要な部分になってくると思います。

こちらは`GCD`などと調べると詳しく解説されたページがたくさん出てきます。

### データの永続化
SwiftUIで外部DBなどに依存させずスタンドアロンなアプリを作る場合、`CoreData`という仕組みを使うことになります。
Webアプリ開発で使う`MySQL`や`SQLite`を想定していると、使うまでに必要な前提知識を理解するのが難しい印象です。

ただ最近`SwiftData`というものが使えるようになってようで、`CoreData`よりも簡単に扱えるようになっていました。
`CoreData`は利用するハードルが初心者にとっては高い印象でしたので、`SwiftData`は初心者にとってもアプリ開発のハードルを下げてくれそうです。

## 何を作ったのか
最後に何を作ったのか紹介します。

私はChatGPTに質問を投げる機会が結構あります。
GitHub上で検索してみると素晴らしいChatGPTのデスクトップアプリケーションはいくつも存在しています。
ただ、どのアプリも基本的にはチャットでの対話型のUIとなっています。
しかし、例えばある言葉の定義を知りたい時に、いちいちチャット対話画面を毎回開くというのは少々めんどうくさいです。
ランチャーなどでテキストを入力してそれに回答を受け取れればそれが一番利便性が高いと思いました。

そこでSwiftUIでChatGPTへメッセージを送信し、その回答をstream方式(ChatGPTが回答を細かく返してくる方式)のレスポンスで受け取りつつ動的にビューを画面上に作って表示するアプリを作りました。

そして今回はURLスキームを使って`open quickgpt://chat/"hello world"`(`quickgpt`はアプリ名)でChatGPTにメッセージ送信できるようにすることで、自由に呼び出し元を選べるようにしました。
私は`Alfred`というランチャーアプリのWorkflowという機能で上記コマンドを加工して、ランチャーアプリからメッセージを送れるようにして使っています。

![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982597/fc9d1d22-27dc-1878-d459-792f07b5e880.gif)

### 工夫した点
- steam方式に対応させて、レスポンスを受け取るたびにビューのテキストを更新してリアルタイム性を高くした
- 回答を表示するビューを好きな位置に移動できるようにした
- ビューの移動位置を保持するようにして、ビューの位置に一貫性を持たせた
- マルチモニターに対応している(はず)
- ステータスバーを活用して、アプリのビュー自体に無駄なボタンを設置しないようにした
- アプリのURLスキームを使ってChatGPTにメッセージを送れるようにし、呼び出し元を縛ることのないアプリにした

### TODO
- 回答が返ってきた時にmacOSのスピーチ機能で読み上げできるようにしたかったが、macOSのバージョンアップデートで動かなくなってしまったので修正が必要

https://github.com/uchidakeisuke/QuickGPT

### 終わり
今回SwiftUIでmacOSアプリを作ってみましたが、やはりネイティヴな操作を行うことができるため、普段の手元での操作やタスクを効率化するには非常にパワフルだと思いました。
ただしWebアプリ開発者視点からすると、やはりWebアプリ開発とは当然ですが事情が全然違うところは多く、学習コストは高めだと感じました。