StackViewを賢く使ってらくちんAutoLayout


『アプリ道場 Advent Calendar 2015』8日目は「Auto Layoutが好き過ぎて、StackViewをおかずにご飯3杯いける」ゆこびん(<a href="https://twitter.com/yucovin">@yucovin</a>)がお送りします。
そもそもイラストレーター／デザイナーなので、アプリの開発をしていても見た目を司るAuto Layoutが気になって気になってしょうがないです。（これは恋？！）

ということで、本日は今年綺羅星のごとく現れたAuto Layout界の期待のクラス「UIStackView」についてまとめたいと思います！(๑´ㅂ`๑)

---


UIStackViewは一言で言うと、便利なレイアウト用の箱です。
StackViewは２つ以上の縦または横に並ぶサブビューを面倒な制約（Constraints）を作ることなく、いいカンジにレイアウトしてくれます。
StackViewを上手く使えば制約の数をうんと少なくすることができるんです。

![Kobito.tvIISh.png](https://qiita-image-store.s3.amazonaws.com/0/96678/52493579-b410-739e-7069-156b4ebc40ab.png "Kobito.tvIISh.png")




# StackViewとは？
それでは改めてUIStackViewについて見ていきましょう。
UIStackViewとは、今年2015年に発表されたiOS 9以降で使えるAuto Layout用ラッパーのクラスです。
stackとは積み重ねること。
StackViewに縦方向又は横方向に並んだサブビューを持たせ、管理します。サブビューの寄せやサイズのバランス、ビュー同士の間のスペースなどを設することで、狙ったレイアウトを作ることができます。



![Kobito.LMeLhf.png](https://qiita-image-store.s3.amazonaws.com/0/96678/79cc2400-9e6e-6870-ccaa-66c74c79d119.png "Kobito.LMeLhf.png")


これにより、Auto Layoutの制約の記述が簡潔でわかりやすくなります。レイアウトのためだけに作っていたダミーのViewを減らすこともできるでしょう。


※StackViewのサブビューは複数個である必要はありませんが、レイアウト上は2つ以上でStackViewを使う意味がでてきます。

※StackViewのレイアウトは、中身に対して有効なので、StackView自体のAuto Layoutは必要になります。



# StackViewの作り方
それでは、早速StackViewを作ってみましょう。作り方はいろいろあります。
簡単なのはStoryboard上で作る方法ですね。

## Storyboard（Interface Builder）上でStackView作る（1）
1)まず、Storyboard上で部品をならべて、StackViewに入れたい部品をすべて選択します。
2)右下のStackボタンを押すと、選択した部品をサブビューにしたStackViewができあがります。
![Kobito.izqKfK.png](https://qiita-image-store.s3.amazonaws.com/0/96678/a7164fd3-0f1f-47c4-0d5e-54ce8b046a5e.png "Kobito.izqKfK.png")


図は横に並べていますが、縦に並べてもOK。適宜、水平垂直どちらかのStackViewができあがります。

## Storyboard（Interface Builder）上でStackView作る（2）
1)StackViewが部品としてObject Libraryにあるので、そこからドラッグアンドドロップ。StackViewには、Horizontal（水平方向）とVertical（垂直方向）とあります。
2)StackViewに入れたい部品をStackViewの中にドラッグアンドドロップ。
![Kobito.a35gEB.png](https://qiita-image-store.s3.amazonaws.com/0/96678/a01e0d1e-5d10-c721-cf98-ee34632a4a13.png "Kobito.a35gEB.png")


## コードでStackViewを作る
もちろん、コードでもStackViewを作ることができます。コチラの方法は後述します。
そのためにもまずUIStackViewの属性などを簡単に知っておく必要があります。



# StackViewに設定する属性
UIStackViewを使う際に設定が必要な属性があります。

```
axis: UILayoutConstraintAxis
alignment: UIStackViewAlignment
distribution: UIStackViewDistribution
spacing: CGFloat
```
StackViewを選択してAttribute Inspectorを見ると一番上に設定があります。

![Kobito.MyxRRz.png](https://qiita-image-store.s3.amazonaws.com/0/96678/f502e8ea-283b-1f61-5dc4-2ddab8f38991.png "Kobito.MyxRRz.png")



これらについて順番に説明します。

## Axis

axisは軸の意ですね。StackViewのサブビューを垂直（縦）方向に並べるか、水平（横）方向に並べるか、のどちらかです。

* Horizontal //水平方向
* Vertical //垂直方向


これは、最初に出てきたStackViewの図に示めした通りです。

![Kobito.NT2p6A.png](https://qiita-image-store.s3.amazonaws.com/0/96678/d709dac1-6def-571e-6f8a-119328f54b99.png "Kobito.NT2p6A.png")



## Alignment

alignmentは揃えですね。
中のサブビューたちをどこに揃えて配置するか？です。

![Kobito.CERauy.png](https://qiita-image-store.s3.amazonaws.com/0/96678/667fa88d-c56e-c419-133b-1b6b56f7575c.png "Kobito.CERauy.png")

alignmentは全部で8種類です。

* Fill
* Leading
* Top
* FirstBaseline
* Center
* Trailing
* Bottom
* LastBaseline


axisがHorizontalかVerticalで使えるものが違います。

まずはalignmentの少ないVerticalから。具体的にはこうなります。

※わかりやすいように、各部品にbackgroundColorを設定しています
![Kobito.8YpQgq.png](https://qiita-image-store.s3.amazonaws.com/0/96678/038806ac-45b2-f961-d8d1-8adc57ddcccb.png "Kobito.8YpQgq.png")


次にHorizontalのalignmet。ベースライン系があります。

![Kobito.I0XOgP.png](https://qiita-image-store.s3.amazonaws.com/0/96678/3c4e3013-fdd3-9c3c-17a6-f7d35773f923.png "Kobito.I0XOgP.png")



※Fillは、Stack Viewが持っているViewのサイズを設定していても関係なく可能なサイズまで大きくします。



## Distribution

distributionはサブビューの軸方向のレイアウトを決めます。ちょっとわかりづらいかもしれませんね。こんなカンジです。

![Kobito.zpnPyk.png](https://qiita-image-store.s3.amazonaws.com/0/96678/be9575ef-efb4-f5fa-15a0-00494bd19083.png "Kobito.zpnPyk.png")




distributionの種類は５つ

* Fill
* FillEqually
* FillProportionally
* EqualSpacing
* EqualCentering

パッ見ではよくわからないものがあるので、少し丁寧に説明します。
（WWDCの発表時、FillとFillProportionallyの違いがよくわかりませんでした。実際、結果的には大きな違いがないことがほとんどかもしれませんが、狙った通りのレイアウトを作るためにはある程度の理解は必要かなと。）


### Fill
Fillは、StackViewの中がいっぱいになるようにサブビュー（部品）の幅（axisがHorizontalの場合）や高さ（axisがVerticalの場合）をとります。
サブビューを配置してStackView内が余るならサブビューの幅（高さ）を増やさないと（伸ばさないと）いけなくなりますが、その際、各サブビューの「Hugging Priority」の優先度にしたがって各サブビューのサイズが大きくなります。逆にサブビューの幅（高さ）が大きくてStackViewから溢れてしまった場合は、各サブビューの「Compression Resistance Priority」の優先度にしたがって各サブビューの幅（高さ）を縮めます。
各部品が同じ優先度になっているなど、曖昧な場合は、StackViewが持っているarrangedSubviewsの配列順に優先度が決まります。（※後述しますが、StackViewはサブビューをarrangedSubviewsの配列として持っています。）


参照）Hugging PriorityとCompression Resistance PriorityはSize Inspectorから設定できます。

![Kobito.Jut20r.png](https://qiita-image-store.s3.amazonaws.com/0/96678/64519954-0c8d-927e-f5cf-5eeeb4e44241.png "Kobito.Jut20r.png")



### FillEqually
FillEquallyはStackViewいっぱいにサブビュー（部品）を配置します。その際、サブビューの幅（又は高さ）は設定に関係なく等しくなります。

![Kobito.NTqaYG.png](https://qiita-image-store.s3.amazonaws.com/0/96678/c7c1fd12-7f18-8017-a0b3-470fa6a22fae.png "Kobito.NTqaYG.png")


### FillProportionally
FillProportionallyもStackViewいっぱいにサブビュー（部品）を配置します。サブビューでStackViewがあふれた場合も余った場合も、各サブビューが持っているコンテンツサイズ（intrinsic content size）にを基にサブビューをリサイズします。

※viewは自分のあるべきサイズを知っています。intrinsicContentSizeメソッドでサイズを取ることができ、Auto Layoutもこれを利用しています。
参照）[UIView Class Reference / intrinsicContentSize](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIView_Class/index.html#//apple_ref/occ/instm/UIView/intrinsicContentSize)




### EqualSpacing

EqualSpacingはサブビューとサブビューの間を等しく配置します。サブビューの幅（高さ）がStackViewより大きい場合は、意味がありません。

![Kobito.8LwERe.png](https://qiita-image-store.s3.amazonaws.com/0/96678/3d800137-9b82-0ff4-9321-3ccfee787110.png "Kobito.8LwERe.png")


### EqualCentering
EqualCenteringは、各サブビューの中心から中心の距離を等しく配置します。

![Kobito.lXPUXl.png](https://qiita-image-store.s3.amazonaws.com/0/96678/4e84ae15-b248-2628-5bc0-62760f9b5487.png "Kobito.lXPUXl.png")



## Spacing
spacingはサブビューとサブビューの間の設定です。


![Kobito.SYw9Qo.png](https://qiita-image-store.s3.amazonaws.com/0/96678/f71be41e-438a-7362-0d12-dee875cc8658.png "Kobito.SYw9Qo.png")


Baseline Relativeにチェックをいれると、垂直方向の基準がベースラインになります。
![Kobito.JDQjSn.png](https://qiita-image-store.s3.amazonaws.com/0/96678/ea36b67b-6084-1e32-0124-c58fe212fd54.png "Kobito.JDQjSn.png")





# ここが知りたいStackView！　〜サブビューは配列で持っている 他〜
もうひとつ、UIStackViewについて突っ込んだお話をするとStackViewは中のサブビューたちをarrangedSubviewsという配列の形で持っています。


```
var arrangedSubviews: [UIView] { get }
```


arrangedSubviewsにviewを追加するメソッド


```
func addArrangedSubview(_ view: UIView)
```

指定したindexにview挿入するメソッド

```
func insertArrangedSubview(_ view: UIView, atIndex stackIndex: Int)   
```



配列からviewを削除するメソッド


```
func removeArrangedSubview(_ view: UIView)
```


ここにひとつ落とし穴というか、勘違いしやすいのですが、配列から削除しただけでは、サブビューはなくなりません。あくまでarrangedSubviewsからいなくなるだけなので、サブビューを消したければ、removeFromSuperviewなどでサブビュー自身を消す必要があります。


他に動的にStackViewの中身を作る場合に知っておくと地味に便利なのが、StackViewのサブビュー（arrangedSubviewsの中身）はhiddenでアニメーションができるようになること。（通常Viewのhiddenはアニメーションできない）。StackViewへの追加や削除のアニメーションを簡単につくれます。



# StackViewをコードで作る方法

さて、ここまで説明するとコードでもStackViewを作れるようになりますね。

早速コードで書いてみましょう


```
// StackViewをつくる
let stack = UIStackView()
stack.axis = .Horizontal
stack.alignment = .Center
stack.distribution = .Fill
stack.spacing = 8
        
// StackViewに入れるサブビュー、部品を作る      
let label = UILabel()
numLabel.text = "ラベル"
numLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
        
let deleteButton = UIButton(type: UIButtonType.RoundedRect)
deleteButton.setTitle("ボタン", forState: UIControlState.Normal)
deleteButton.addTarget(self, action: "pushButton:", forControlEvents: UIControlEvents.TouchUpInside)

// StackViewにサブビューを追加
stack.addArrangedSubview(label)
stack.addArrangedSubview(deleteButton)


// StackViewを画面など好きなところに
view.addSubview(stack)


```

コードで作るの自体は簡単ですね。
ただ、普通にAuto LayoutのConstraintsもそうですが、StackViewをコードで書いて画面を作るのは実際大変そうですね。一つや２つなら大したことがないかもしれませんが、入れ子にしていくと頭がこんがらがりそうです。その場合は、普通にConstraintsを書いていった方がラクかもしれません。








# StackVieｗの特徴、メリットデメリット


StackViewの諸々が分かってきたと思いますので、ここで簡単なStackViewの例を挙げてみたいと思います。
同じレイアウトをAuto LayoutのConstraintsで設定したもの（左）とStackViewを利用してつくったもの（右）です。

Constraintsだけで作ったものは各Viewごとに位置の縦方向横方向サイズを決める制約を作っています。

![Kobito.Kne2pt.png](https://qiita-image-store.s3.amazonaws.com/0/96678/83ef141c-9e13-5dad-1e7d-7ca8aa4aa639.png "Kobito.Kne2pt.png")

StackViewを使ったものは、StackView自身の制約を３つばかりつくっていますが、あとはAttribute Inspectorで設定しただけです。
![Kobito.1dQWgi.png](https://qiita-image-store.s3.amazonaws.com/0/96678/8391d351-5f7d-9de2-7641-b1cce3cd9b5d.png "Kobito.1dQWgi.png")


これは端的な例ですが、サブビューの数が増えれば増える程、StackViewの恩恵を多く感じるようになるでしょう。
だだ、各Viewの間の幅をもっと細かく設定したいという時は、StackViewの簡便さが邪魔になります。


次の例は、もう少し複雑な画面です。
やはり同じレイアウトを普通にConstraintsで作ったもの（左）と、StackViewを利用して作ったもの（右）です。

![Kobito.qlzg6p.png](https://qiita-image-store.s3.amazonaws.com/0/96678/e26df72f-1236-de20-df9d-571fe9a2df5d.png "Kobito.qlzg6p.png")


StackViewを入れ子に使っています。
StackViewでも思い通りのレイアウトを作るには各ViweのサイズのConstraintsは必要になってきますし、ViewによってはHugging PriorityとCompression Resistance Priorityの優先度をしっかり確認しておかないといけません。
が、StackViewごとにレイアウトを作っていけるので、制約だけでつくったものより、レイアウト構造を把握しやすくなります。
イメージとしては、制約だけで作る場合は「一つのフォルダに全部のルールが入っている」、StackViewを使うと「ディレクトリ構造でルールが整理されている」というカンジでしょうか。


![Kobito.0QK3NB.png](https://qiita-image-store.s3.amazonaws.com/0/96678/196cc1a6-c2b2-bb17-f55a-2496caa65a65.png "Kobito.0QK3NB.png")








# StackViewを使う時のポイント
一歩進んでStackViewを上手に使うためのポイントをいくつか。
### 優先度を設定する

何度も出てきましたが、Hugging PriorityとCompression Resistance Priorityの優先度をちゃんと設定することで、どんな画面サイズでも狙った通りのレイアウトが作れます。

### 入れ子にして使う
これは、私の経験ですが、StackViewは小さな単位で入れ子にして使うとさらに威力を発揮します。
StackViewを使う前は、複数のViewをまとめるイメージがあり、何となく３つ以上のViewに対して使うと便利！と思い込んでいました。が、実際使ってみると、２つのViewが並んでいるところに対してStackViewを作って、それを更に隣のViewと組み合わせ…と、入れ子にしていくとStackViewの良さが活かされるシーンが多いと気が付きました。
（※上の具体例に挙げた少し複雑な画面の例が２つのサブビューを持つStackViewの入れ子構造になっています。）







# まとめ

最後にUIStackViewのメリットとデメリットをまとめます。

◯　Auto Layoutがすっきりする、Constraintsを減らせる
◯　レイアウトの構造を把握しやすいので複雑な画面も作りやすい

×　細かいレイアウト（ルールがたくさん必要なもの）はStackViewでは対応しきれない、かえって煩雑になる可能性がある
×　iOS 9以降から


StackView、知れば知るほどすごいヤツです。今まで地味に作っていたNSLayoutConstraintがかなり減らせそうですよ。
StackViewはとっても便利ですが、「これひとつで完璧」な代物ではないので、自分の作りたいレイアウトによってStackViewの使いどころを判断するといいですね！


では！ステキなAuto Layoutライフを！




## 参考
[UIStackView Class Reference](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/index.html#//apple_ref/swift/enum/c:@E@UIStackViewAlignment"UIStackView Class Reference")
[Auto Layout Guide]
(https://developer.apple.com/jp/documentation/UserExperience/Conceptual/AutoLayoutGuide/GettingStarted/GettingStarted.html "Auto Layout Guide")