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

## はじめに

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

前日は [@kokiueda](https://note.mu/koki) さんの「[ぼくがかんがえたさいきょうのOverridableなSketchシンボルのつくりかた（序章）](https://note.mu/koki/n/nf6a6f6169ec9)」でした。

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


ですが，その前にまず，ある一人の開発者のお話をします。

## ある開発者の話

良くある Storyboard の失敗例です。*1

ある開発者は，新しい View を作成することになりました。ちょっとこねくり回して (作成者だけは) 頑張ったと思い込んでいる Storyboard の様子です。

<img width="806" alt="ss 2018-12-11 at 1.08.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/ed3f808c-303d-d46d-54d9-f44147b8702f.png">

しばらくして，「メンテナンス中」を示すアラートを表示することになりました。
その開発者は「ただ UIView と UILabel を置くだけだから」と，その「上」に View を作成してしまいました。

<img width="821" alt="ss 2018-12-11 at 1.10.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/53182891-7f50-e700-d077-e33d56734be6.png">

またしばらくして，「こんなアラートも出さなきゃ!」と，別の種類のアラートも表示することになりました。
その開発者は再び，「ただ UIView と UILabel を置くだけだから」と，その「上」に View を作成してしまいました。

<img width="810" alt="ss 2018-12-11 at 1.11.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/3af6a63d-836c-d4ff-5d75-b0467c06427f.png">


その数ヶ月後，仕様変更が生じ，この Storyboard の Label を変更することになったそうです。また，View の上部にある Missing Constraints に起因するバグ報告も上がってきました。

この View を担当していた開発者は一体どうなったでしょうか。その行方を知る者はいません。。。

## どうすれば良かったのか

継続的な開発を進めていく以上，仕様変更から逃れることはできません。
ですので，View の変更に柔軟に対応できる設計を予め組み，変更に「備える」必要があります。

今回は Atomic design (アトミックデザイン) を利用します。

## 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 が構成されています。

## 達成したいこと

達成したいことは，Storyboard の機能を最大限に活かし，**View の視認性を向上させ，変更に柔軟に対応できる設計にすること** です。

一方で， **適度に Override した View を避ける設計にすること** も守っていきます。

過度に override された View を利用した場合，管理が大変になり，変更に耐えられなくなる事態が往々にして発生致します。
1つの Storyboard に 80以上 の Viewが存在し，4層以上に階層化された Storyboard なんて通常誰もメンテナンスしたくなくなるはずです。

再利用可能で変更しやすいプログラムにするために，コード単位ではObject指向からProtocol指向に，
設計では分離しやすいレイヤードアーキテクチャ，クリーンアーキテクチャ等が採用されるようになってきています。

Atomic degsign に沿って個々に分離した Xib を作成していくことで，再利用性と変更に強い View を作成していこうと思います。

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

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">

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

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

#### Atom の作成

まずは Atom の作成です。`ActionCountableView` という UIView を作成します。
この段階では，個々のコンポーネントの数が少なく，プロパティが他に及ぼす影響が少ないため Xib ファイルから設定できるプロパティはできるだけ UI 上で指定しています。

今回は UICollectionView の Cell に使用するため，`prepareForReuse()` が呼びやすいように `ContentResettable` という protocol を作成しておき，その時点で再利用時に初期化が必要な View について初期化できるようにしておきます。

```swift
public protocol ContentResettable {
    func resetContents()
}
```

タップによってハイライト画像を順に切り変えるなど，ビジネスロジックに関わらず共通な機能があればここに記述します。
特にない場合は不要です。その場合 .swift ファイルの中身は以下のようにとてもシンプルな実装となります。最後に Outlet 接続をして完成です。

<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">

#### Molecule の作成

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

Atomic Design に従った場合，こちらも同様に変更することが可能です。
**ここで重要な点は，作成したAtomをXibファイルからコピペして再利用する** ということです。

これにより，**Outlet 接続と Constraint を引き継ぎつつ**，新たな View を作成していくことができるようになります。
Atom である `ActionCountableView` は `ContentResettable` に適合しているため，再利用する View の管理は `resetContents()` を呼ぶだけで完了します。

<img width="1385" alt="ss 2018-12-11 at 10.43.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/9a0d4409-ec50-45d3-34bf-1789deeb1ac4.png">

#### Organism の作成

Organism の作成をします。今回は UICollectionViewCell を利用しました。この分離は簡略しましたが，この View が他で利用され得る場合は，UIView を挟んだ方が良いです。

Molecule の作成時には，既に View 単位のコンポーネントが揃いつつある状態だと思われます。ここで StackView を利用すると，View の増減に対応できるようになります。
ここまでに Auto layout が適切に設定されていれば，ここで設定するプロパティは **親とのConstraint程度** になります。

<img width="1387" alt="ss 2018-12-11 at 10.58.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/bbdd42f9-5b63-8e0a-1f5d-2eb2910d87b3.png">

#### Template の作成

複数の UIViewController から成る場合に必要だと考えていますが，今回は1つで1つの画面を担当していたため，省略致します。

#### Page の作成

最後に Page です。Page は UIViewController を利用するため，Storyboard で作成していきます。
最低限の UICollectionView をはじめ，必要な Outlet を配置した後，ViewModel や Presenter と接続していきます。

View を作成していった場合，以下のように **何が，どのように配置されているのか** という見通しがとてもしやすい Storyboard になっていることが分かります。

UICollectionView の場合，ここで配置した Cell を 利用時にセルを初期化してあげることで，テンプレートとしての役割を果たすことも可能です。

<img width="1314" alt="ss 2018-12-11 at 11.43.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/1b7683c8-b7a2-d0da-1418-4d379268894f.png">

## 良かったところ

親の変更を子が知らないという依存性を保ったまま，変更が容易になりました。
Storyboard の難点であった，View の階層構造によって見通しが悪くなる問題が解決されました。
また，View が完成する様子が Storyboard で確認することができ，モチベーションを維持することが可能とりました。

何よりも，Sketch との差分を都度比較する作業が少なくなり，小手先の修正にかかる時間が大幅に短縮されました。

## つらいところ

もちろん，限界もあります。

### Sketch の限界問題

Sketch 機能自体の限界です。

CSS 出力は可能な Sketch ですが，現在 Storyboard 作成機能はありませんので，Sketch データを元に新たに作成する必要があります。

### Sketch 既定シンボルの問題

また，Sketch の通常のテキストオブジェクトは上下の余白が存在しません。一方で，UILabel は余白がデフォルトで設定されているため，Constraint をマイナスに指定する必要があるなど，1対1 で利用することが少し難しいです。
対応として，Apple 公式 [Apple Design Resources](https://developer.apple.com/design/resources/) から UIKit の Sketch 用コンポーネントをダウンロードし，iOS UI Design からシンボルを利用することで，適切な幅の View を利用することが可能です。デザイナーに対して Sketch の設定と HIG ([Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)) への理解を協力していただく必要があります。

<img width="400" alt="ss 2018-12-11 at 2.26.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/95eef932-ce07-2523-d2a8-4871ff911465.png">

### ファイル数が多くなる問題

Xib ファイルが増えるのはどうしようもないです。これは変更をしやすくする設計にはつきものですので，現状は諦めています。

### View の変更が追従しない問題

もちろんですが，View の指定は Xib ファイルから行われるため，変更が他の View に反映されない問題が生じます。
これによって解決するのは「階層化する View に対処する」ことまでで，Atom を利用する View が増えるほど，その変更が大きくなってしまいます。

一方で，View が分離していることから，階層自体の透明性は担保されており，View (Page) を作成する頃には親との View の関係を意識する程度で良くなるため，
これらのメリットと比較した場合，仕方のない部分なのかなぁと思います。

### Xib のコピペ時に外側の Constraints が破棄される問題

親の View との Constraint を設定している場合，子 View のみコピーしてしまうと，親-子間の Constraint は破棄されてしまいます。
通常はトップの View をコピーするため問題は生じないのですが，Cell を Xib で利用した場合，Constraint が Cell と紐付くと，親 Cell に対して指定した Constraint を別の View に対して適用させることが難しくなります。こればかりはどうしようもありませんでした。

## まとめ

綺麗な Sketch 要素が存在すれば，複雑な View も数時間あれば完成します。
今回は時間の都合上，複雑なヘッダ部分は省略しましたが，Sketch に限りなく近付いた実装を，Storyboard を用いて作成することができました。

以下が完成した Storyboard です。多くの View から成り立ちつつも，どう階層化されているのかが非常に分かりやすくなっており，とても見通しが良くなりました。
Sketch とも綺麗に一致しており，コードレイアウトをするひとつの理由でもあった「どこで変更が適用されているのか分からない」という問題も，Atom の存在により解決されています。

Sketch は Auto layout にも対応しています。Sketch だけでマルチデバイス対応他， UI の多くの部分を対応できるので，その資産を Storyboard にも反映していくことができれば，より良い開発ができるのではないでしょうか。

<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">

## References

[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/)

## 注釈

※1 この物語はフィクションです。