iOS開発でのViewのレイアウトの考え方


## なりゆき
チーム開発をしていて、レイアウトに関する考え方がバラバラだったのでみんなの意識を統一するために一定の方針を考えました。
ちなみにここで言っているレイアウトはデザイン的なことではなく、実際に開発する際にどうやって画面を作るかという意味でのレイアウトになります。

ちなみにどんな問題が起きてたかというと...

- あるViewの位置を変更したら他のViewもずれた
- ViewControllerに全てのViewに対するレイアウト処理がびっしり書かれていて辛い

## 方針
画面のレイアウトを考える時に以下の方針に従い設計していきます。

1. View同士の階層を考える
1. 階層化したViewを一つのカスタムViewとして扱う
1. レイアウトを決めてよいのは1階層下のViewまで

## 1. View同士の階層を考える

『階層化したViewを一つのカスタムViewとして扱う』『レイアウトを決めてよいのは1階層下のViewまで』を考えるにはまず 
**『View同士の階層を考える』** 必要があります。

View同士の階層を考えるためにViewをグルーピングしていきます。

例として[アキネーター](https://itunes.apple.com/jp/app/akinator-the-genie/id484090401?mt=8)のスクリーンショットを使わせてもらっています。
 
下記の画面を作る時と仮定して書いていきます。
(おじさんと右上のホームアイコンはないものとして説明していきます)
![akinator_example01.png](https://qiita-image-store.s3.amazonaws.com/0/56178/81ea9169-f240-db2a-8c5d-e131ae138592.png "akinator_example01.png")

### 1.1 グルーピング1回目
画面全体のView(おそらくViewControllerのview)がまず大きなグループになります。
黒枠に囲まれた部分です。これを仮にBaseViewとします。
![akinator_example02.png](https://qiita-image-store.s3.amazonaws.com/0/56178/21e6c606-05aa-abdd-39e4-420d8b6d6f0c.png "akinator_example02.png")

### 1.2 グルーピング2回目
次に緑色のViewと赤色のViewと青色のViewに大きく分けることができます。
それぞれ以下とします。

- 緑色→SerifView
- 赤色→CharacterTypeView
- 青色→StartView

![akinator_example03.png](https://qiita-image-store.s3.amazonaws.com/0/56178/21431b58-00ec-3adc-eb22-0d936a2886d1.png "akinator_example03.png")

この時点で以下のような階層があると考えられます。
![hierarchy01.png](https://qiita-image-store.s3.amazonaws.com/0/56178/55a76900-e463-0d96-b9ce-5b480e64bff0.png "hierarchy01.png")

### 1.3 グルーピング3回目

緑色のView(SerifView)の中をオレンジ色のViewとピンクのViewに分けることができます。

- オレンジ色→TextView
- ピンク色→ExpView

同じように赤色のView(CharacterTypeView)の中は以下のように分けることができます。

- 紫色→TitleView
- 水色→FamousLabelView
- 黄緑→FamousCheckView
- 黄色→MyWorldLabelView
- 白色→MyWorldCheckView

![akinator_example04.png](https://qiita-image-store.s3.amazonaws.com/0/56178/0802fbf1-aa41-a492-ed01-fcc546c7d239.png "akinator_example04.png")

この時点で親子関係は以下のようになります。
![hierarchy02.png](https://qiita-image-store.s3.amazonaws.com/0/56178/55e7ceff-f8b8-77a2-6e57-80e56809c3a9.png "hierarchy02.png")

上記を繰り返してグルーピングできなくなるまで実施します。
例ではキャプチャに自分で色を付けてやっていますが、実際に開発している時はノートに絵を描いてやっています。

こんな感じで。
![note.JPG](https://qiita-image-store.s3.amazonaws.com/0/56178/2a7512d2-285b-c25f-e888-23c97c5e301a.jpeg "note.JPG")

## 2. 階層化したViewを一つのカスタムViewとして扱う
**基本的に子を持つViewはカスタムViewにすべきです。**

今回の例で言うと、 **SerifView** と **CharacterTypeView** はそれぞれUIViewクラスを継承したカスタムクラスを作成します。
こうすることでBaseViewはSerifViewとCharacterTypeViewとStartViewだけを持てば(addSubviewすれば)よくなります。
つまりBaseViewはTextViewやTitleViewを知らなくて良くなります。

```Objc:BaseView.m
// TextViewやTitleViewを持つ必要がない
[self addSubView:serifView];
[self addSubView:characterTypeView];
[self addSubView:startView];
```

BaseViewが見える階層は以下
![hierarchy01.png](https://qiita-image-store.s3.amazonaws.com/0/56178/55a76900-e463-0d96-b9ce-5b480e64bff0.png "hierarchy01.png")

## 3. レイアウトを決めてよいのは1階層下のViewまで

### ダメなこと

- 兄弟が別の兄弟のレイアウトを決める

![brother_ng.png](https://qiita-image-store.s3.amazonaws.com/0/56178/dd73dbef-b3a9-f98b-d773-27a8f8c53217.png "brother_ng.png")

```Objc:SerifView.m
// 兄弟が別の兄弟のレイアウトを決める
// そもそもStartViewを参照できるのもおかしいけど
self.startView.frame = (0,0,self.view.frame.width,self.view.frame.height);
```

- 子が親のレイアウトを決める

![childToParent.png](https://qiita-image-store.s3.amazonaws.com/0/56178/15c9ec13-c7b3-2ebd-fd6f-eb486175a552.png "childToParent.png")

```Objc:CharacterTypeView.m
// 子が親のレイアウトを決める
// そもそも参照できるのもおかしいけど
self.baseView.frame = (0,0,self.view.frame.width,self.view.frame.height);
```

- 親が孫のレイアウトを決める

![skipChild.png](https://qiita-image-store.s3.amazonaws.com/0/56178/2f23d7af-eba1-4cf3-8694-798ef8b7b7a7.png "skipChild.png")

 
```Objc:BaseView.m
// 親が孫のレイアウトを決める
// そもそも参照できないように設計するべき
self.textView.frame = (0,0,self.view.frame.width,self.view.frame.height);
// とか
self.serifView.textView.frame = (0,0,self.view.frame.width,self.view.frame.height);
```

### していいこと

- **親が子のレイアウトを決める**

```Objc:BaseView.m
// serifViewはBaseViewの子なのでこれはOK
self.serifView.frame = CGRectMake(0,0,self.view.frame.width,self.view.frame.height);
```

今回の例で言うとBaseViewはSerifViewとCharacterTypeViewとStartViewのみレイアウトを決めることができます。

下の図はBaseViewから見たレイアウトの可否の図です
![hierarchy03.png](https://qiita-image-store.s3.amazonaws.com/0/56178/94038b9a-5dc9-b51e-ad15-36e3243dad03.png "hierarchy03.png")


SerifViewから見たレイアウトの可否の図は以下
![hierarchy04.png](https://qiita-image-store.s3.amazonaws.com/0/56178/359ab674-5331-5129-b0ae-d6a8b2f8311b.png "hierarchy04.png")

## 方針に従うことによる利点

- 1階層下のViewのみのレイアウトを管理することで、孫のViewを変えたことで親や子のレイアウトが崩れることがなくなる
- 親のコードが完結になる(孫のレイアウトがなくなるので)

## まとめ

チーム開発ではViewのレイアウトに関して今回のように階層を意識して設計することで、責務が階層ごとに分割されメンテしやすいコードになりました。