Sketchと1対1を目指すAtomic designなSwiftUIの作り方

## はじめに

[VALU Advent Calendar 2019](https://qiita.com/advent-calendar/2019/valu) 11日目 (!) の記事です!

VALU ではデザインに Atomic design (アトミックデザイン) を採用しています。
本記事では，VALU のデザイナーが作成した素敵な Sketch シンボルを差分なく反映し，かつ変更に耐えうる SwiftUI を運用するための方法をご紹介しようと思います。

[昨年の VALU Advent Calendar](https://qiita.com/advent-calendar/2018/valu) にて，[Sketchと1対1を目指すAtomic designなStoryboardの作り方](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29) を投稿しました。本記事はそれを SwiftUI で行ったものです。まだご覧になっていない方は先にご覧ください。

## Highlights

- SwiftUI を利用することで，「ファイル数増加」「子View更新時の伝播」「親ViewのConstraints破棄」など，UIKit ([昨年の実装](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29)) での懸念点が全て解決された
- Atomic design x SwiftUI の親和性はとても高く，ほとんど1対1で再現することができた
- SwiftUI が迫る今，我々 iOS エンジニアはどう闘っていくのだろうか

## 昨年の実装方法を振り返る

昨年作成した Storyboard および Xib を以下に改めて掲載します。
Sketch の Level と同様に，細かい単位で Xib ファイルを生成した後，StackView を利用することにより，増減に強い UI の作成が可能となりました。
一見完全に再現できているように見える一方で，[View の変更が追従しない問題](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29#view-%E3%81%AE%E5%A4%89%E6%9B%B4%E3%81%8C%E8%BF%BD%E5%BE%93%E3%81%97%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C) など，開発する上での限界も存在していました。
これらの問題は，SwiftUI によって解決されたのでしょうか?

<img width="1491" alt="ss 2018-12-11 at 0.39.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/43a9ef3b-efd9-8345-30d4-c5f6a066e9b9.png">
<img width="1680" alt="ss 2018-12-11 at 2.16.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/977b8ae6-2abc-ab9d-7719-c3eb247d2f34.png">

## Atomic にファイルを分割することによって発生した問題

Atom レベルから Xib と，それに対応する Swift ファイルを生成したことにより，[ファイル数が多くなる問題](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%95%B0%E3%81%8C%E5%A4%9A%E3%81%8F%E3%81%AA%E3%82%8B%E5%95%8F%E9%A1%8C) が生じます。
この問題は，最終的にコンパイルさえ通らなくなるという予期せぬ事態を生む結果となりました。

本件につきましては，別の記事で詳細とその解決策を記しています。Atomic design 自体が悪かった，ということではないエラーでしたが，情報共有のため以下に掲載しておきます。

[【Xcode】細分化する iOS Architecture に向き合う上で気をつけなければらないただひとつのエラーについて](https://qiita.com/YutoMizutani/items/ae6282dee4d14b59996e)

## Atomic design (アトミックデザイン) とは

![atomic-design.png](http://bradfrost.com/wp-content/uploads/2013/06/atomic-design.png)

[Atomic Design](http://atomicdesign.bradfrost.com/table-of-contents/) で有名なこのイラスト。上のイラストはそれを端的に説明しています。

Atomic Design とは，ひとつのページを個々の細かなコンポーネントを組み合わせて作成するデザインの方針を指します。

Atom (原子)，Molecules (分子)，Organisms (生体)，Templates，Pages

その Atomic (原子-の) の名と矢印の方向が指す通り，個々の小さな汎用部品を元に，より大きな View が構成されています。

## Sketch に合わせた View ファイルの作成

Sketch では，Atom，Molecules，といった表現が「Lv」によって示されています。
低レベルのシンボルを元に，より大きなシンボルを作成していく形となっています。

以下は，弊社デザイナーさんから受け取った，素敵な Sketch ファイルです。

<img width="1491" alt="ss 2018-12-11 at 0.38.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/2bd07791-6644-8d28-8ff6-37c8c1054738.png">
<img width="1491" alt="ss 2018-12-11 at 0.38.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/3f7f064b-3be1-a64d-89ae-720644aec096.png">
<img width="1491" alt="ss 2018-12-11 at 0.33.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/dfad279d-7dab-df1d-9a1f-74e92688d9a7.png">

これを，シンボル毎に SwiftUI ファイルにしていきます。

今回は前回同様，複数の View から構成され，いいね等のアクションを行なう画面 (Lv2/Post/Action/Icon Button) に焦点を当てていきます。

## Atom の作成

まずは Atom の作成です。`ActionCountableView` という View を作成します。

<img width="1395" alt="ss 2018-12-11 at 11.03.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/5e651d74-5a47-7605-3bb9-4bb65b38397e.png">

最初だけ振り返りつつ進みます。昨年の実装は上の通り，Xib ファイルと Swift ファイルを用意し，IB 上またはコード上で値を指定していました。

以下が SwiftUI にて作成した内容です。SwiftUI は，左側のコードと右側のプレビューが対応しており，プレビュー画面での変更が即時にコードとして反映されます。ですので，編集するファイルは `.swift` ファイルのみとなります。

<img width="1383" alt="screenshot 483.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/992f32be-6602-6529-bf69-3360b6e27b19.png">

`#if DEBUG` で囲まれた部分はデバッグ用のプレビューに当たる部分であり，View の構成に必要な struct 部分はたった 20 行で表現できます (Xib を使った昨年の実装は Xwift ファイル 12 行 + Xib ファイル 50 行 → 2 ファイル 62 行)。

驚くほど簡単に View を生成することができたのが分かるでしょう。さらに，プレビュー用の記述を追加することで，画像の差し替えや値のランダム化，ダークモード時の様子まで確認することができるようになっています。

これだけでもいくつか SwiftUI としてのテクニックがあります。

1 . `Button` のハイライト領域

SwiftUI では，ボタンは `Button` 構造体を利用しています。いくつか初期化方法がありますが，ボタンとして認識させ，かつタップ領域としてハイライトさせるためには，`Button<Label> : View where Label : View` の `Label` 部分がボタン UI として認識されます。`Label` は protocol `View` に適合した Generics となっています (SwiftUI では UIKit で言う `UILabel` は `Text` に相当します)。
`init(action: @escaping () -> Void, @ViewBuilder label: () -> Label)` を利用して **ボタン内部に View を描く** ことによって，ハイライト領域を指定しています。

2 . `Image` の色変更およびサイズ変更

`.renderingMode(_:)` を `.template` 指定することにより，色を変更することができます。
また，`.frame(width:height:)` でサイズを変更する前には，`.resizable()` を入れサイズが可変であることを明示的に指定しなければなりません。

3 . `static func` によるプレビュー内再利用

ダークモード対応など，同じ Atom の内容を複数の環境で一覧したい場合があるかと思います。プレビューに用いられる `static var previews: some View` は static ですので，再利用したい View を別の static な変数または関数に切り出しておくと，スタブを注入する手間も省けるためお得です。

## Molecule の作成

次は，先ほど作成した Atom を複数組み合わせた Molecule に当たる View を作成していきます。
Sketch では，この View に対して Override を活用しつつそれぞれの画像を挿入していました。

Atomic Design に従った場合，こちらも同様に変更することが可能です。

昨年と異なる点は，*値を保ちつつ複数の画面にコードを共有させることができる*※ 点です。
Xib のコピペから開放されることで，子 View での変更が 親 View への変更に常に追従できるようになります。これは UI がコードのみから生成できるようになった利点でもあります。

UIKit の `StackView` のように `HStack` に積んでいるだけですが，`Spacer()` を利用することで，長さ指定のない `UIView` 同様伸縮するようになります。

<img width="1388" alt="screenshot 484.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/7c79761d-88dc-a95c-5576-8df93a44235c.png">

※ `@IBDesignable` を利用した生成方法等によってこれまでも Atoms または Molecules レベルのソースコードを共有することができました。一方でその方法は，Xcode のコンパイル時間が長くなってしまったり，ソースコードの量によって最終的には IB 上で描画が実行されなくなってしまうなどの問題があり，昨年度は採用しなかった経緯があります。

## Organism の作成

Organisms に入ります。ここで一旦内容を以下のような struct にまとめておきます。後にデータをリスト状に表示させるため，`Hashable` にも適合させることが必要です (ここでは処理を簡略にするため`Image` を直接プロパティとして持たせていますが，Domain 層にある API 通信の結果だと想像してください)。

<img width="400" alt="screenshot 494.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/0a65c03d-13d1-f46c-2df7-2d4634ad3d79.png">

投稿内容を反映する `PostView` を作成します。個々の View は Molecules において作成済みですので，VStack を利用して詰み上げるだけで完成します。

<img width="1376" alt="screenshot 496.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/5d99edf3-9b40-c2fb-d483-7c303d175c29.png">

投稿画像など，存在しない可能性がある場合は `AnyView` と `EmptyView` の組み合わせによって動的に実現が可能です。三項演算子を利用していますが，どちらの View も `AnyView` でラップしてあげることで結果を返し，異なる View を切り替えることができます。

## Page の作成

最後に，Page の作成です。
`ScrollView` 内部に `ForEach` を作成し，`Hashable` に適合した `Post` 構造体をもとに投稿内容を反映しています。ダークモードにも対応すると，プレビューも映えますね。

画像を見れば分かる通り，高さは内容によって自動で調整が入るようになっています。ここまで来れば，もう描画についての責任は Pages から離れることとなり，スクロール処理や遷移等の動作に関わる状態に集中できるようになるでしょう。

実際の開発では，ここから `@State` として記述している部分を，`@ObservedObject var viewModel` に置き替え，通信部分や画面遷移の処理を書いていくことになります。

<img width="1920" alt="screenshot 495.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/1d4ce95c-5650-10f1-b38a-1c82c4c7c9fc.png">

## まとめ

<img height="200" src="https://qiita-image-store.s3.amazonaws.com/0/195913/43a9ef3b-efd9-8345-30d4-c5f6a066e9b9.png"><img height="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/9d7bdc2e-b3fe-c911-7e53-d61e934d6ab5.png"><img height ="200"  src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/195913/ad3aa667-98b0-0f91-e71a-1320e9d49e55.png">

今回は，過去に使ったものと同じデザインを利用し，SwiftUI を用いて改めて Atomic design に向き合いました。
その結果，SwiftUI を利用することで，「ファイル数増加」「子 View 更新時の伝播」「親 View の Constraints 破棄」など，昨年の UIKit での懸念点が全て解決され，View をデザイン通りに記述することができるようになりました。

さらに，UIKit を用いるよりも格段に Sketch に近づいた印象です。これがコードのみで表現されるのは当時は想像もつかなかったのではないでしょうか。これ，HTML で言う CSS を使ってないんですよ!?
Atomic design x SwiftUI の親和性はとても高く，React の知見と上手く融合していくのだと感じました。
これからコード生成プラグインが React のように SwiftUI にも対応するような流れになれば，UI 部分はもっと進化していくのではないでしょうか。


一方で，危惧されることが 1 点あります。
（SwiftUI で 1 アプリを作った程度の人間でも）数時間でデザイナーの意図する画面を 1 対 1 として容易に実現可能な点です。これはひとつに，デザイナーが UI 層に進出することを容易に想像できる1つの証拠です。

現在の SwiftUI では，スクロール時のページング処理など，ちゃんとした機能を利用しようとすると View が大変複雑になります。Atomic design との親和性は高まったものの，どこまで似せるのか，SwiftUI の得意不得意にどう対応していくのか。

簡単な UI であれば誰でも作れるようになった世界において，領域を奪われた iOS エンジニアの勝負どころはどこになるでしょうか。証明書周りやリジェクト対応と答えた人はセンスがあると思います。実際には ViewModel 以後ですよね。そうなった際にエンジニアとしての価値が再度問われるわけです。


*あなたはどうですか？何ができますか？どこまでできますか？* と。

## References

[Sketchと1対1を目指すAtomic designなStoryboardの作り方](https://qiita.com/YutoMizutani/items/e14c918232af83f34e29)
[Atomic design](http://atomicdesign.bradfrost.com/table-of-contents/)
[Apple Design Resources](https://developer.apple.com/design/resources/)
[Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)
[センスのある某での呟き](https://twitter.com/EXPENSIVE_MAN/status/1193227121918787584)