toio コア キューブの状態を SwiftUI で表示してみた

## はじめに

日頃の業務や趣味では TypeScript を使うことが多いですが、最近は個人的な興味で、Swift を使った iOS アプリの開発を始めています。
まだ Swift の文法や流儀などよくわかっていない状況ではありますが、いろいろ調べる中で「SwiftUI」が便利そうだと感じ、今回試しに、[toio コア キューブ](https://toio.io/) (以下、キューブ) の位置や向きの表示に使ってみました。

下図は今回作った iOS アプリのスクリーンショットです。
<img width="414" alt="IMG_851D7CD86E95-1.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141406/1c26c9a4-fcd8-d0d3-b8a9-f354e2306b4d.jpeg">
画面の上半分で、接続中のキューブをリスト形式で表示しています。
キューブは 2台以上接続することもでき、画面右上の「Add」を押すたびに、リスト項目が増えます。
リスト項目はそれぞれ 1台のキューブを示していて、最新の「(x座標, y座標), 向き」を表示します。右端のトグルで、キューブとの接続・切断を制御できます。

画面下半分には「Forward (前進)」といったボタンが並び、接続中の複数キューブを同時に前進させるなど制御することができます。

　ソースコード
　　https://github.com/eqot/toio-sample.swift

なお本記事では、SwiftUI の利用に主眼を置いていることから、iOS アプリにおける BLE 利用方法といった、SwiftUI と直接関係ないトピックの記載は割愛しています。興味ある方は、書籍「[iOS×BLE Core Bluetoothプログラミング](https://www.amazon.co.jp/iOS%C3%97BLE-Core-Bluetooth%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E5%A0%A4-%E4%BF%AE%E4%B8%80/dp/4883379736)」など参照してください。

## SwiftUI とは

SwiftUI は、Apple が 2019年 6月の開発者向けイベント「WWDC19」で発表したフレームワークで、シンプルな記述で UI を定義できます。

　SwiftUI
　　https://developer.apple.com/jp/xcode/swiftui/
![hero-lockup-medium_2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141406/ab93b5d1-6310-dcad-5142-844a65aa843f.png)

### 宣言型プログラミング

SwiftUI の特長のひとつは、いわゆる「[宣言型 (declarative) プログラミング](https://ja.wikipedia.org/wiki/%E5%AE%A3%E8%A8%80%E5%9E%8B%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)」で、一般的なプログラミングで使われる「命令型 (imperative) プログラミング」とは記述内容や考え方が異なります。

命令型プログラミングでは、開発者が実現したいことを「どのように実現するか」を記述します。たとえば、画面に文字と画像を表示したいときは、

```swift
    canvas.drawText("foo", 100, 200)
    canvas.drawImage(barImage, 300, 200)
```

といったように、「どのクラスのどの機能を使うか」を開発者が意識し、これらをうまく使いこなす必要があります。

一方の宣言型プログラミングでは、「開発者が実現したいことは何か」を記述します。

```swift
    Text("foo")
    Image("barImage")
```

このとき、「どのクラスのどの機能を使うか」はフレームワークが適切に処理するため、開発者は意識する必要がなくなります。これによって、表示する画面の大きさや操作体系が異なる場合 (macOS アプリ、iOS アプリ、watchOS アプリなど) や、将来的により高度な機能が備わった場合にも、アプリにおける UI 定義は変えることなく、フレームワークによって最適な処理を実現できます。

宣言型のひとつである HTML が、ブラウザの種類が異なっても、古いブラウザでも新しいブラウザでも、(概ね) 同じ表示になることから実感できるかと思います。

### データバインディング

SwiftUI と同時に、非同期処理を扱う Combine フレームワークも追加されました。
Combine フレームワークは SwiftUI と連携することもでき、UI に表示するデータをシンプルな実装で UI と紐付けて、UI 操作に応じてデータを更新したり、更新されたデータを UI に反映することができます。

具体的な実装を見てみましょう。

```swift
import Combine

public class CoreCube: ObservableObject {
        (略)

    @Published public var x: Int = 0
    @Published public var y: Int = 0
    @Published public var direction: Int = 0

        (略)
}
```

上記は、キューブを管理・制御するクラス ```CoreCube``` の一部です。メンバー変数の ```x```、```y```、```direction``` は、マットの上でのキューブの位置や向きに応じて適宜変化します。クラスが ```ObservableObject``` を継承し、これらのメンバー変数に ```@Published``` をつけることで、値の変化を監視することができるようになります。

値の変化を捉え、UI に表示する実装は以下のようになります。

```swift
import SwiftUI

struct CoreCubeRow: View {
    @ObservedObject var cube: CoreCube

    var body: some View {
        Text("(\(self.cube.x), \(self.cube.y)), \(self.cube.direction)")
    }
}
```

構造体 ```CoreCubeRow``` は、値変化の監視対象となるインスタンス ```cube``` を保持します。このとき ```@ObservedObject``` を宣言しているところがポイントで、これによって値変化を契機に ```CoreCubeRow``` が再描画され、UI 上には常に最新の位置と向きが表示されるようになります。

ここでも宣言型プログラミングが使われています。
従来の命令型プログラミングでは、フレームワークが提供する、もしくは自前の Observer / Listener クラスを使うなどして、データと UI を紐付ける必要がありました。宣言型プログラミングでは、開発者が「どのような仕組みを使うか」を意識する必要はなく、実現したいことを非常にシンプルな記述で定義できます。


別の例も見てみましょう。

今回試作したアプリでは、複数台のキューブへ接続したかったため、複数台のキューブを管理するクラス ```CoreCubeGroup``` を定義しています。

```swift
public class CoreCubeGroup: ObservableObject {
    @Published var cubes = [CoreCube]()
    
    func add() {
        cubes.append(CoreCube())
    }
}
```

```CoreCube``` のインスタンスを配列として保持するメンバー変数 ```cubes``` を定義しています。先述の ```@Published``` をこの配列に記述し、項目が増える (＝ 接続中のキューブが増える) などの変化を監視します。

この変化を反映する UI 側の実装は以下のようになります。

```swift
import SwiftUI

public struct CoreCubeList: View {
    @ObservedObject var cubes: CoreCubeGroup

    public var body: some View {
        List(self.cubes.cubes) { cube in
            CoreCubeRow(cube: cube)
        }
    }
}
```

構造体 ```CoreCubeList``` は、CoreCubeGroup のインスタンスを保持し、接続中のキューブをリスト形式で表示します。
接続中のキューブが増えたことを契機に ```CoreCubeList``` が再描画され、接続中であるすべてのキューブが表示されます。

## まとめ

SwiftUI を使って、接続中のキューブをリスト形式で表示し、キューブそれぞれの位置や向きを表示してみました。

今後は、SwiftUI で定義した UI を watchOS 上でも表示させてみるなど、SwiftUI ならではのメリットを活かした実装も試してみたいと思っています。


## 参考サイト

* Apple 公式
  * [SwiftUI Tutorials](https://developer.apple.com/tutorials/swiftui) (英語)
  * [WWDC 2019 動画](https://developer.apple.com/videos/wwdc2019/?q=SwiftUI) (英語)
* [【iOS】Combineフレームワークまとめ](https://qiita.com/shiz/items/5efac86479db77a52ccc)