SwiftUIにおける拡張。そして再発明の危険性

# SwiftUIの特徴

SwiftUIを書いていると、VStackやHStackなどが登場しまるでUIを軽快なDSLの上で構築している気持ちになる。あながち間違っているわけではないが、SwiftUIはあくまで「宣言」の羅列であり実際にビューの構築を行う訳ではないことに注意しなくてはならない。

例えば`refreshable`はListにPullToRefreshを付与するモディファイアであるが、それはあくまで実行して得られた結果に過ぎない。

![CleanShot 2022-03-05 at 05.14.00@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24979/53849d84-d2bb-2d6e-8ce4-bf1e396ace08.png)


この挙動が今後どうなるかをユーザーはコントロールすることが出来ないのだ。
これはどういうことかというと、今後AppleがOS全体の更新を「端末をシェイクする」としたとする。
すると、アプリのUIはコードを変更せずとも`PullToRefresh`のコントロールが消えてシェイクで更新に変わるのだ。

これはSwiftUIに限った話ではなく、UIKitでもOSのアップデートによってスクロールバーの高速移動機能[^1] が付いたりナビゲーションのジャンプ機能[^2]が付いたりした。
しかしSwiftUIはUIKitに比べて、宣言時の表現が非常に緩い。
名前からしても`UIRefreshControl`が急にシェイクに変わることはない[^3]が、`refreshable`がシェイクに変わることは全然あり得る話なのだ。

なぜこのようなことになっているかというと、既にアプリのUIコンポーネントは抽象化が難しいほどに姿を変えるようになってしまったからだ。

ゲームやwebの世界からすると意外かもしれないが[^4]、アプリのボタンはユーザーの設定によって大きく変化する。
分かりやすい例で言えばキーボードは、ユーザーがハードウェアのキーボードを接続しているか否かでソフトウェアキーボードが画面上に表示されるか否かが決定する。
これと同じように、ユーザーが大きな文字に設定していればボタンの大きさは自由に変化し、色盲のユーザーは色を変え、弱視のユーザーはボタンの周りにボーダーを引き、アニメーション酔いしてしまうユーザーはアニメーションや曇りガラス効果を無効化する。
さらにAppleプラットフォームではアプリはハードウェアの垣根を超えて、macOSやtvOSに最適な表示になる。
このどれもが同じコードで動作する。
このような多様なユーザーに対して適切なUIを表示する基盤がアプリの標準UIなのだ。

![IMG_0812.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24979/9cc33e65-2840-8f99-02fd-0d09f962f0b7.png)

つまり、これらに厳密な名前をつけることに意味は無くなってしまった。
「ボタン」であれば、「ボタン」なだけで良いのだ。「影のついた青色のタップした時にアニメーションするボタン」のような限定的でユーザーの自由を奪うUIを発明する必要がなくなったのだ。
故にSwiftUIではかなり抽象度を上げた命名が行われていると思われる。

# UIを再発明することの危険性

例えば、SwiftUIでスイッチのUIを作るとする。
SwiftUIには標準で`Toggle`というViewが用意されている。しかし、このToggleは味気なく新鮮味に欠ける。
SwiftUIのRoundRectなどを駆使して、あなたは自作の`CustomToggle`を開発した。
この`CustomToggle`はアプリの世界観にマッチしていて、何よりオシャレだ。

アプリを作っているとこのようなことが往々にしてあると思う。
けれど、もし本当に良いアプリを作りたいのであればグッと我慢することを強くお勧めしたい。
大抵の場合、このようなUIコンポーネントは

> アプリの世界観にマッチしていて、何よりオシャレ

これくらいの価値しかない。
この価値を得るために、どんなトレードオフがあったか考えてみる。

- スイッチの装飾オプションに対応していない
- macOSで動かした時も同じUIなので、マウスで押しにくいUIになってしまった。
- タップでしか操作できない
- 読み上げでオンかオフか読み上げられない
- 大きさが常に一定で、人によっては押しにくい
- アラビア語でも向きが一緒
...etc

パッと思いつくだけでもこれだけある。
基本的にはハリボテのカスタムUIはマジョリティには受け入れられる。しかし、これらのマイノリティのUXを一度犠牲にしたら、チームは絶対に優先度を上げることは出来なくなる。

全部実装するくらいなら`Toggle`を使った方がどう考えても良いと考えるか、これらのユーザーを無視して開発するかは自由だ。
でも、UIを再発明するときは実際は多くのトレードオフが孕んでいることを理解しなくてはいけない。

# SwiftUIにおける拡張

では、全部シンプルなボタンを配置してつまらない見た目のアプリにして良いのか。
答えはYESだと私は思っている。体験こそアプリの本質で、体験が良ければ過剰な装飾は不要だと思っている。[^5]

しかし、実際にはボタンの色に意味を持たせたり、余白によってユーザーの気持ちを整理する必要が出たりする。
そのような用途のために、SwiftUIはstyleという機構を持っている。
styleはButtonStyleやListStyleに代表される見た目を変更するためのモディファイアで、これらを使う分には、SwiftUIは将来的に破綻しないUIを提供してくれる。

SwiftUIを使うときは、このstyleの中で拡張が完結するように意識して作ると良いと思う。
間違ってもbackgroundColorが変更できないからと言ってZStackで上に色を置いたりしてはいけない。[^6]
繰り返しになるが、それはあくまで今その実行環境で綺麗に動いているように見えるだけで、ユーザーやOSが変わると破綻することがある。
特にSwiftUIは、破綻したUIをきめ細やかに補強するのが苦手でもある。なのでトリッキーなことは最初からしてはいけない。

ここまで理解をすると、SwiftUIのStackはあくまでUIコンポーネントのレイアウトを決定するもので、UIコンポーネントを開発するためのものではない事が分かる。


# UIの発明

SwiftUIやUIKitに存在しないUIは、どうしてもUIを発明して実装が必要になることがある。
（前提として、全ての標準UIやHIGや他のアプリを観察して大替の方法がないかを吟味した上で）

再発明でないUIは、結論作るしかない。
先ほど書いた通り、UIを最初から作るのは考慮することが多いので非常に難しい。
必要になった時に想定以上の工数がかかるのを回避するために、`アプリのコア体験に関わる部分`にだけは、発明をしても良いと私は思う。

例えばお絵描きアプリはペンで描画する部分は0から作る。
それはアプリの特徴や強みにもなるので、前向きな側面もある。

# まとめ

SwiftUIにおける拡張と再発明の危険性について、思っていることを書いてみました。
一昔前はPullToRefreshやスワイプセルなどのスマートフォンに最適化したジェスチャーやUIの発明が活発的に行われており、それが良いこととされていました。
しかし、UIの統一化やユーザーに最適化する観点が洗練された現代ではOSの敷いたレールに乗ることが推奨されます。
これはiOSやAndroidと言ったプラットフォームの上で熟成されたもので、canvasに直接UIを描画するゲームエンジンやUIエンジンから見ると意外に感じるかもしれません。
ユーザーは全てのアプリで、自分の特徴に合わせてUIが最適化されることを望んでいます。
そしてエンジニアやUIデザイナーは、ハリボテを作る仕事から情報の設計や整理をする仕事に求められる能力がシフトします。
この記事が皆さんの今後のUI開発の参考になればと思います。









[^1]: https://www.youtube.com/watch?v=3Dcrs-jI0fY
[^2]: https://www.iphonetricks.org/long-press-back-button-in-settings/
[^3]: シェイクはインタラクションだと思うのでControlという名前のまま変わらんだろうなぁと
[^4]: エアプなので全然意外じゃないかもしれない
[^5]: つまらない見た目といいつつ、SwiftUIで普通にアプリを作るとコンプレクション・リダクションの効いたトレンディなデザインになる　https://uxmilk.jp/48364
[^6]: ちなみにUIAppearanceもNG https://qiita.com/noppefoxwolf/items/5ba81e93bef8b91f1484