Xcode
iOS
AutoLayout
Swift
iOS9

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

More than 3 years have passed since last update.

『アプリ道場 Advent Calendar 2015』8日目は「Auto Layoutが好き過ぎて、StackViewをおかずにご飯3杯いける」ゆこびん(@yucovin)がお送りします。

そもそもイラストレーター/デザイナーなので、アプリの開発をしていても見た目を司るAuto Layoutが気になって気になってしょうがないです。(これは恋?!)

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


UIStackViewは一言で言うと、便利なレイアウト用の箱です。

StackViewは2つ以上の縦または横に並ぶサブビューを面倒な制約(Constraints)を作ることなく、いいカンジにレイアウトしてくれます。

StackViewを上手く使えば制約の数をうんと少なくすることができるんです。

Kobito.tvIISh.png


StackViewとは?

それでは改めてUIStackViewについて見ていきましょう。

UIStackViewとは、今年2015年に発表されたiOS 9以降で使えるAuto Layout用ラッパーのクラスです。

stackとは積み重ねること。

StackViewに縦方向又は横方向に並んだサブビューを持たせ、管理します。サブビューの寄せやサイズのバランス、ビュー同士の間のスペースなどを設することで、狙ったレイアウトを作ることができます。

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

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


Storyboard(Interface Builder)上でStackView作る(2)

1)StackViewが部品としてObject Libraryにあるので、そこからドラッグアンドドロップ。StackViewには、Horizontal(水平方向)とVertical(垂直方向)とあります。

2)StackViewに入れたい部品をStackViewの中にドラッグアンドドロップ。

Kobito.a35gEB.png


コードでStackViewを作る

もちろん、コードでもStackViewを作ることができます。コチラの方法は後述します。

そのためにもまずUIStackViewの属性などを簡単に知っておく必要があります。


StackViewに設定する属性

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

axis: UILayoutConstraintAxis

alignment: UIStackViewAlignment
distribution: UIStackViewDistribution
spacing: CGFloat

StackViewを選択してAttribute Inspectorを見ると一番上に設定があります。

Kobito.MyxRRz.png

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


Axis

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


  • Horizontal //水平方向

  • Vertical //垂直方向

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

Kobito.NT2p6A.png


Alignment

alignmentは揃えですね。

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

Kobito.CERauy.png

alignmentは全部で8種類です。


  • Fill

  • Leading

  • Top

  • FirstBaseline

  • Center

  • Trailing

  • Bottom

  • LastBaseline

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

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

※わかりやすいように、各部品にbackgroundColorを設定しています

Kobito.8YpQgq.png

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

Kobito.I0XOgP.png

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


Distribution

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

Kobito.zpnPyk.png

distributionの種類は5つ


  • 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


FillEqually

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

Kobito.NTqaYG.png


FillProportionally

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

※viewは自分のあるべきサイズを知っています。intrinsicContentSizeメソッドでサイズを取ることができ、Auto Layoutもこれを利用しています。

参照)UIView Class Reference / intrinsicContentSize


EqualSpacing

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

Kobito.8LwERe.png


EqualCentering

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

Kobito.lXPUXl.png


Spacing

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

Kobito.SYw9Qo.png

Baseline Relativeにチェックをいれると、垂直方向の基準がベースラインになります。

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をコードで書いて画面を作るのは実際大変そうですね。一つや2つなら大したことがないかもしれませんが、入れ子にしていくと頭がこんがらがりそうです。その場合は、普通にConstraintsを書いていった方がラクかもしれません。


StackViewの特徴、メリットデメリット

StackViewの諸々が分かってきたと思いますので、ここで簡単なStackViewの例を挙げてみたいと思います。

同じレイアウトをAuto LayoutのConstraintsで設定したもの(左)とStackViewを利用してつくったもの(右)です。

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

Kobito.Kne2pt.png

StackViewを使ったものは、StackView自身の制約を3つばかりつくっていますが、あとはAttribute Inspectorで設定しただけです。

Kobito.1dQWgi.png

これは端的な例ですが、サブビューの数が増えれば増える程、StackViewの恩恵を多く感じるようになるでしょう。

だだ、各Viewの間の幅をもっと細かく設定したいという時は、StackViewの簡便さが邪魔になります。

次の例は、もう少し複雑な画面です。

やはり同じレイアウトを普通にConstraintsで作ったもの(左)と、StackViewを利用して作ったもの(右)です。

Kobito.qlzg6p.png

StackViewを入れ子に使っています。

StackViewでも思い通りのレイアウトを作るには各ViweのサイズのConstraintsは必要になってきますし、ViewによってはHugging PriorityとCompression Resistance Priorityの優先度をしっかり確認しておかないといけません。

が、StackViewごとにレイアウトを作っていけるので、制約だけでつくったものより、レイアウト構造を把握しやすくなります。

イメージとしては、制約だけで作る場合は「一つのフォルダに全部のルールが入っている」、StackViewを使うと「ディレクトリ構造でルールが整理されている」というカンジでしょうか。

Kobito.0QK3NB.png


StackViewを使う時のポイント

一歩進んでStackViewを上手に使うためのポイントをいくつか。


優先度を設定する

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


入れ子にして使う

これは、私の経験ですが、StackViewは小さな単位で入れ子にして使うとさらに威力を発揮します。

StackViewを使う前は、複数のViewをまとめるイメージがあり、何となく3つ以上のViewに対して使うと便利!と思い込んでいました。が、実際使ってみると、2つのViewが並んでいるところに対してStackViewを作って、それを更に隣のViewと組み合わせ…と、入れ子にしていくとStackViewの良さが活かされるシーンが多いと気が付きました。

(※上の具体例に挙げた少し複雑な画面の例が2つのサブビューを持つStackViewの入れ子構造になっています。)


まとめ

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

◯ Auto Layoutがすっきりする、Constraintsを減らせる

◯ レイアウトの構造を把握しやすいので複雑な画面も作りやすい

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

× iOS 9以降から

StackView、知れば知るほどすごいヤツです。今まで地味に作っていたNSLayoutConstraintがかなり減らせそうですよ。

StackViewはとっても便利ですが、「これひとつで完璧」な代物ではないので、自分の作りたいレイアウトによってStackViewの使いどころを判断するといいですね!

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


参考

UIStackView Class Reference

Auto Layout Guide