普通の画面をUIStackViewでよりロバストに

# UIStackView

[「怖くない！AutoLayout 〜多画面対応 with Storyboard〜」](https://qiita.com/_ha1f/items/5c292bb6a4617da60d4f)
ぶりのAutoLayout関連の記事です。

UIStackViewは複数のViewを上に積んだり横に並べたり、iOSのレイアウトではかなり有用に使えることができるので、かなり便利に使うことができます。
iOS 9で登場した比較的新しいViewですが、現在大抵のアプリはiOS 9以降しかサポートしてないと思うので、問題なく利用できると思います。

HTML5のFlexboxの代替！と言うにはまだまだ力不足かなと思っていますが、
従来の生のレイアウトよりは変更に強いように実装しやすく、実際ほとんどの画面で使っています。

楽に使える分、自動で変わってしまって思い通りに動かないことが多いと思いますが、
Tipをいくつかご紹介します。
プロパティを説明すると言うより、自分の使い方を紹介します。

# 縦(vertical)と横(horizontal)

一つのUIStackViewでは、縦もしくは横に積むことしかできません。

向きはAxisというプロパティで決めます。

iOSの画面だと基本的に縦に並べることが多いと思うので、一つ大きなverticalなUIStackViewを持って、必要なところはhorizontalなUIStackViewをネストする、みたいなそんな使い方をしています。

同じ向き同士でネストさせることもできますが、扱いにくいのでおすすめしません。

# Alignment、Distribution

verticalな物を使う場合が多いと思うので、以降、そちらをベースに説明します！
horizontalに関しては適宜読み替えてください。

<img width="338" alt="53d50917c72c9d656976fc7850dfdd66.png" src="https://qiita-image-store.s3.amazonaws.com/0/77778/dac56c04-365b-677e-9057-76d99c103a91.png">

StoryboardでUIStackViewのプロパティを見てみると、先に述べたAxisのほかに、Alignment、Distributionがあります。
Alignmentは横の配置、DistributionとSpacingは縦の配置に関するプロパティです。

### Alignment

`.fill` は、すべてのサブビューを横いっぱいに広げるときに使います。この場合は、それぞれのサブビューに横幅の制約を付ける必要はありません。
それ以外（ `.leading`, `.center`, `.trailing` ）では、サブビューの横幅を維持して左/中央/右に配置します。
すなわち、横幅の制約が別に必要になります。

横幅の付け方は、widthの制約を追加するか、左右からの余白（.centerならどちらか、.leadingまたは.trailingなら逆側）を指定します。
個人的には、`.fill` と同じ状態にするには、 サブビューとUIStackViewにEqual widthの制約をつければよいです。

※ leading, trailingは正確には左/右ではなく、文字を書き始める方向の話で、日本語ではleadingが左ですが、アラビア語などでは逆になります。

### Distribution, Spacing

各プロパティについては、こちらの記事([UIStackViewの5つのdistributionを理解する](https://qiita.com/taka1068/items/69273f05d34cfbeb3679))が詳しいです。

まず、どんなプロパティをセットしていても、UIStackViewの中身は、すべてサブビューで埋める必要があります。AutoLayoutによる制約以外で、余白を作ることはできません。
（個人的に辛いと思う部分の一つです）

上の制約のために、勝手に高さを書き換えられてしまう場合があります。

基本的に高さは変えられたくないと思うので、サブビューに高さの制約を入れると良いです。さらに、UIStackView自体の高さをコンテンツによって変えることで、自動で高さを変えられることを回避できます。（次章で説明します）

また、同じ要素を配置する場合を除いて、スペースが全部同じとか間隔が同じとか、そういうことってあまりないように思いますので、個人的なおすすめは、`.equalSpacing`にして、`Spacing`を0にすることです。
そしてスペースは、そのsubviewのtopもしくはbottomのspacingのheightに制約をつけるか、透明なUIViewを挟むことで作っています。

注意点として、
spacingで制約を付ける場合、楽ですが、その対象のオブジェクトがhiddenなどで消えない必要があります。
透明なUIViewを挟む場合は、その周辺のオブジェクトをhiddenなどするときに、どこの余白を消すべきか考える必要があります。（spacerも関連付ける必要がある）

Storyboardでは関連付けしていないViewにもカスタムネームをつけることができるので、 `Spacer`という名前をつけることでわかりやすくしています。

僕はcontainerを作って、containerのvisiblityを変えることでまとめてisHiddenを切り替えることが多いです。containerを作れば、上下どっちもに対してspacerのviewを挟む代わりにレイアウトできるので便利です（spacingの制約をつける）

# UIStackViewのサイズをコンテンツに応じて変える

先程書いたように、UIStackViewは中身をすべて埋める必要があります。
サブビューの高さを変更されないようにするためには、UIStackView自体の高さをコンテンツに応じてしかありません。

方法は簡単です。一番下のViewと、UIStackViewのBottom EdgesをAlignすることです。

<img width="421" alt="スクリーンショット 2019-03-28 19.17.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/77778/0c20aa59-1338-856a-f4e8-d21e7fb4dd16.png">

余白を付けたい場合は、0ではない値にすると良いです。

一番下の要素がhiddenなどで消える可能性がある場合は、一番下にUIViewを追加して、heightを0としてconstraintして、それとbottomを合わせると良いです。

とはいえこれは特別必要な場合だけで良くて、UIStackViewを使えば普通は勝手に下端に揃うようになります。
（contentCompressionRegistancePriorityなどによる）

# UIStackViewの背景色

UIStackViewには背景色などをつけることはできません。
UIStackViewは箱にのみ使うべきで、レイアウト対象以外のsubviewなどは親Viewに追加するべきです。

# UIScrollViewも合わせて

結局横画面対応とかキーボード対応とかでUIScrollViewが必要なことも多くて、
いつもこんな感じの画面構成です。（適当なサンプルです）

<img width="1063" alt="7bf8297b64f60459fe92801b66411399.png" src="https://qiita-image-store.s3.amazonaws.com/0/77778/c7622174-8ab1-38a4-82fd-b0e033d7ef50.png">

Keyboard対応とかも合わせて、UIScrollViewとUIStackViewの組み合わせTipについても、以下の記事に書きましたので、
こちらもぜひご参考に！

[iOSで入力ボックスがある画面はだいたいこうするといいよ！](https://qiita.com/_ha1f/items/a2e285ca67e4dce3a499)