[iOS]UIStackView+ContainerView+UIScrollViewで複雑な画面を設計する

今更なネタですが。

アプリのコンテンツ量が多くなってくると、いろんなコンテンツが混在した画面というものが登場します。最たる例はトップ画面です。

こんな雰囲気。

![Untitled Diagram (31).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/f17a7d4d-2204-8fff-0237-cabdc802ad4d.png)

単純な場合はどのように組んでも問題は生じないのですが、次の場合頭を抱えることになります。

- 横スクロールが伴う
- 一部のコンテンツが、状況によって出たり出なかったりする
- 出たり出なかったりするのがAPIのレスポンス依存（後から決まる）
- APIが別れている、複数ある
- 高さがコンテンツの量に応じて変わる
- 高さがアニメーションにより増減する
- タッチイベントが競合する
- Viewが重い
- 一番下だけ無限スクロールでページングする
- スイッチが有り、コンテンツが切り替わる

こういった画面に遭遇した時、UITableView、UICollectionView、UIStackViewのどれかを使うことを検討すると思います。
そのUIStackViewでどう組むかという話です。

## 対象

UIStackViewがある程度分かる人

## UITableVIewとUICollectionViewではダメなのか？

ダメではないと思います。
しかしこれらはハマりポイントが多数存在しがちです。

- 高さの変更でハマる（特にアニメーション）
- 非同期でAPI取得後、表示・非表示を制御する時にハマる
- Cellの場合は表示直前に生成するため、Cell内のViewが多いとスクロールする際にカクつくことがある（例えばCellの中にUICollectionViewが含まれている場合など）
- Cell内にUICollectionViewがあったりすると、設計に悩むしライフサイクルでも悩む

それでせっかく作ってもあーだこーだ調整に時間が掛かってしまうというのが過去数件ありました。
UIStackViewがこの全てを解決するわけではありませんが、仕様によってはUIStackViewを使うことでスッキリ書けることがあります。

## UIStackViewが向かないもの

同じようなViewが連続する場合はもちろんCellを使ったほうがよいでしょう。
また、コンテンツ量が非常に多い場合なども向きません（といってもこれは本当に多いケースです）
ヘビーな画面を作る際は、画面表示時に1回だけ重いか、Cell表示時に毎回重いかどちらかを選択することになると思います。

## ContainerViewでどのように高さを変更するか問題

これが本題です。

UIStackViewの中にContainerViewを含める場合、高さの制御が問題になります。
なぜか内部のUIViewContollerで自身のViewの高さをAutoLayoutで指定してもUIStackViewの高さに反映されてくれないのです。
できればUITableViewAutomaticDimensionのように、内部側から高さを決めたいですよね。

それを実現するためには、内部のUIViewControllerでこうします。

```swift
override func loadView() {
    super.loadView()
    self.view.translatesAutoresizingMaskIntoConstraints = false
}
```

これで解決します（理由は察せると思うので割愛。何だそんなことかー、と思いました）

## サンプル

https://github.com/osanaikoutarou/EasyStackViewControllers

こういう状態で、ContainerViewをUIStackViewに入れます。
<img width="413" alt="スクリーンショット 2019-06-27 1.50.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/a143763a-a130-79cd-5d5e-b62f6e2ff57c.png">

画面A
UITextViewのScrollableはオフにしています
<img width="428" alt="スクリーンショット 2019-06-27 1.52.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/cb605c76-8cb5-ce45-dd10-e8a5e4834a29.png">

画面B
UIImageViewの高さを固定しています
<img width="429" alt="スクリーンショット 2019-06-27 1.52.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/3ad3c467-a558-d665-bf71-97b5fd883f51.png">

画面C
Addを押すとViewが追加されていきます
<img width="433" alt="スクリーンショット 2019-06-27 1.53.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/4879084e-2cf4-fa76-0eb4-28f6da760b9e.png">

実行結果
![ezgif-2-fa9c8bcdf1c0.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/10093a88-0226-dc5e-a8f6-9433c77433e4.gif)

このように、内部の高さに応じてUIStackView側の高さも変わっています。

一番上のViewControllerの中身はこの状態です↓
空っぽ！

<img width="399" alt="スクリーンショット 2019-06-27 2.03.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/d0646057-3a16-7bf1-e41d-f6cb14943930.png">

## 鬼門：UIScrollView

何度やってもハマるUIScrollViewのAutoLayout
参考にどうぞ

<img width="864" alt="スクリーンショット 2019-06-27 2.06.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/ccec0be3-81ec-8628-f1e4-c8ff39fd2089.png">

UIScrollView　-> 画面view　上下左右
ContentView -> UIScrollView　上下左右+EqualWidth
StackView -> ContentView 上下左右
このままだとエラーが出るので
UIView -> StackView　高さ固定
高さ固定を使わないなら、Remove at build timeにチェック

※UIScrollViewのContentLayoutGuideをOffにしないとエラーになります

<img width="716" alt="スクリーンショット 2019-06-27 2.09.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20784/cccc8ba2-2a9d-9d85-0f45-a61dab7a507b.png">

このViewを使わないなら、viewDidLoadあたりでStackViewの中身を空に
（ここらへんもっとスマートな方法無いんですかね？AutoLayoutのエラーを放置する手もありますが）

## UIStackView in UIStackViewは可能か？

上のサンプルの画面Cで既に使っています
大丈夫です

## 高さアニメーションしたら、見た目が少し変

こちら
[[iOS]UIStackViewのアニメーションが変！](https://qiita.com/netetahito/items/64d9689f371a773f9e2e)

## Viewにするか、ViewControllerにするか迷う

こちら
https://stackoverflow.com/questions/2632196/what-is-the-difference-between-a-view-and-a-view-controller


## 別解

ライブラリーを使うてもあるかも知れません（学習コストが高そうで、まだ使ったことありません）

StackViewController
https://github.com/seedco/StackViewController
AloeStackView
https://github.com/airbnb/AloeStackView

## 積み残した話題

一番下のコンテンツが無限スクロール（ページング）の場合はどうする？🤔
CellとStackViewのコラボレーションは少しハマりどころがあるのでまた別の機会に

あと、パーツをViewControllerにするにはちょっとコスト高いという場合にどう定義するのがスッキリするか、少し悩んでます
（UIStoryboard内にCustomViewを定義して、IBOutletを繋げない問題。Cellなら可能なんですけどねえ）

## 追記

このトピック無いなと思って書いたんですが、ありました。
[巨大なビューをStoryboardだけで表現する](https://qiita.com/imk2o/items/f22db30781f67cb52f9a)

ContainerView周りの話題は認識しておいたほうが良いですね。