スタックビュー
以下のレシピは、スタックビューを使用して、複雑さが増すレイアウトを作成する方法を示しています。スタックビューは、ユーザインターフェイスをすばやく簡単に設計するための強力なツールです。それらの属性により、配置されたビューのレイアウト方法を高度に制御できます。これらの設定を追加のカスタム制約で拡張できます。ただし、これによりレイアウトが複雑になります。
これらのレシピのソースコードを表示するには、 Auto Layout Cookbook (自動レイアウトレシピ集) プロジェクトを参照してください。
簡単なスタックビュー
このレシピは、単一の垂直スタックビューを使用して、ラベル、画像ビュー、およびボタンをレイアウトします。
ビューと制約
Interface Builder で、垂直スタックビューをドラッグして開始し、花のラベル、画像ビュー、編集ボタンを追加します。次に、図のように制約を設定します。
- Stack View.Leading = Superview.LeadingMargin
- Stack View.Trailing = Superview.TrailingMargin
- Stack View.Top = Top Layout Guide.Bottom + Standard
- Bottom Layout Guide.Top = Stack View.Bottom + Standard
属性
属性インスペクタで、以下のスタックビュー属性を設定します。
スタック | 軸 | 整列 | 配布 | 間隔 |
スタックビュー | 垂直 | 満たす | 満たす | 8 |
次に、イメージビューで以下の属性を設定します。
ビュー | 属性 | 値 |
イメージビュー | イメージ | (花の画像) |
イメージビュー | モード | アスペクトフィット |
最後に、サイズインスペクタで、画像ビューの内容が保持する圧縮耐性 (CHCR) の優先順位を設定します。
名前 | 水平が保持する | 垂直が保持する | 水平の抵抗 | 垂直の抵抗 |
イメージビュー | 250 | 249 | 750 | 749 |
議論
スタックビューをスーパービューに固定しなければなりませんが、それ以外の場合、スタックビューは他の明示的な制約なしにレイアウト全体を管理します。
このレシピでは、スタックビューはスーパービューを小さな標準マージンで埋めています。配置されたビューは、スタックビューの境界を満たすようにサイズ変更されます。水平方向に、各ビューはスタックビューの幅に合わせて引き伸ばされます。垂直方向では、ビューはその CHCR の優先順位に基づいて拡大されます。画像ビューは常に利用可能なスペースを満たすために縮小および拡大する必要があります。したがって、その垂直方向のコンテンツにぴったりで圧縮耐性の優先順位は、ラベルとボタンのデフォルトの優先順位よりも低くなければなりません。
最後に、画像ビューのモードをアスペクトフィットに設定します。この設定により、画像のアスペクト比を維持したまま、画像ビューの境界内に収まるように画像ビューで画像のサイズが変更されます。これにより、スタックビューは、イメージを歪めることなく、イメージビューを任意にサイズ変更できます。
スーパービューを埋めるためにビューを固定する方法の詳細については、属性 と 適応するシングルビュー を参照してください。
入れ子になったスタックビュー
このレシピは、入れ子になったスタックビューの複数のレイヤーからビルドされた複雑なレイアウトを示しています。ただし、この例では、スタックビューは必要な動作だけを作成することはできません。代わりに、レイアウトをさらに調整するために追加の制約が必要です。
ビュー階層がビルドされたら、次のセクション ビューと制約 に示す制約を追加します。
ビューと制約
入れ子になったスタックビューで作業する場合、内側から外側へ作業するのが最も簡単です。まず、Interface Builder で名前の行をレイアウトします。ラベルとテキストフィールドを正しい相対位置に配置し、両方を選択して、[エディタ(Editor)] > [埋め込み(Embed In)] > [スタックビュー(Stack View)] メニュー項目をクリックします。これにより、行の水平スタックビューが作成されます。
次に、これらの行を水平に配置して選択し、[エディタ] > [埋め込み] > [スタックビュー] メニュー項目をもう一度クリックします。これにより、行の水平スタックが作成されます。次に示すように、インターフェースのビルドを続けます。
- Root Stack View.Leading = Superview.LeadingMargin
- Root Stack View.Trailing = Superview.TrailingMargin
- Root Stack View.Top = Top Layout Guide.Bottom + 20.0
- Bottom Layout Guide.Top = Root Stack View.Bottom + 20.0
- Image View.Height = Image View.Width
- First Name Text Field.Width = Middle Name Text Field.Width
- First Name Text Field.Width = Last Name Text Field.Width
属性
各スタックには独自の属性の設定があります。これらは、スタックがそのコンテンツをどのようにレイアウトするかを定義します。属性インスペクタで、以下の属性を設定します。
スタック | 軸 | 整列 | 配布 | 行間 |
ファーストネーム | 水平 | 最初のベースライン | 満たす | 8 |
ミドルネーム | 水平 | 最初のベースライン | 満たす | 8 |
ラストネーム | 水平 | 最初のベースライン | 満たす | 8 |
名前の行 | 垂直 | 満たす | 満たす | 8 |
上部 | 水平 | 満たす | 満たす | 8 |
ボタン | 水平 | 最初のベースライン | 均一に満たす | 8 |
ルート | 垂直 | 満たす | 満たす | 8 |
さらに、テキストビューの背景色をライトグレーにします。これにより、向きが変わったときにテキストビューのサイズがどのように変更されるかを簡単に確認できます。
ビュー | 属性 | 値 |
テキストビュー | 背景 | ライトグレー |
最後に、CHCR の優先順位は、利用可能なスペースを満たすために拡大すべきビューはどれかを定義します。サイズインスペクタで、以下の CHCR 優先順位を設定します。
名前 | 横抱き | 垂直抱き | 水平抵抗 | 垂直抵抗 |
イメージビュー | 250 | 250 | 48 | 48 |
テキストビュー | 250 | 249 | 250 | 250 |
ファースト、ミドル、 ラストネームのラベル | 251 | 251 | 750 | 750 |
ファースト、ミドル、 ラストネームの テキストフィールド | 48 | 250 | 749 | 750 |
議論
このレシピでは、スタックビューが連携してほとんどのレイアウトを管理します。ただし、必要な動作をすべて自分で作成することはできません。たとえば、画像ビューのサイズが変更されても、画像はその縦横比 (aspect ratio) を維持する必要があります。残念ながら、簡単なスタックビュー で使用されているテクニックはここでは機能しません。レイアウトは、画像の末端と下端の両方に近づける必要があり、アスペクトフィットモードを使用すると、これらの寸法のいずれか 1 つに余白が追加されます。幸い、この例では、画像のアスペクト比は常に正方形であるため、画像を画像ビューの境界に完全に塗りつぶし、画像ビューを 1:1 のアスペクト比に制約できます。
Interface Builder では、アスペクト比の制約は、単にビューの高さと幅の間の制約です。Interface Builder は、さまざまな方法で制約の乗数を表示することもできます。通常、アスペクト比の制約については、比として表示されます。したがって、View.Width = View.Height 制約は 1:1 のアスペクト比として表示されます。
さらに、すべてのテキストフィールドは同じ幅でなければなりません。残念ながら、それらはすべて個別のスタックビューにあるため、スタックはこれを管理できません。代わりに、等しい幅の制約を明示的に追加しなければなりません。
簡単なスタックビューと同様に、CHCR の優先順位の一部も変更しなければなりません。これらは、スーパークラスの境界が変化したときにビューがどのように縮小および拡大するかを定義します。
垂直方向に、テキストビューを拡張して、上部スタックとボタンスタックの間のスペースを埋めます。したがって、テキストビューの垂直コンテンツハグは、他の垂直コンテンツハグの優先順位よりも低くなければなりません。
水平方向に、ラベルは本来のコンテンツサイズで表示され、テキストフィールドは余白を埋めるためにサイズ変更されます。デフォルトの CHCR 優先順位は、ラベルに適しています。Interface Builder はすでにコンテンツハグを 251 に設定しており、テキストフィールドよりも高くなっています。ただし、テキストコフィールドの水平コンテンツハグと水平圧縮抵抗の両方を低くする必要があります。
画像ビューは、名前行を含むスタックと同じ高さになるように縮小する必要があります。ただし、スタックビューはコンテンツを緩くハグするだけです。これは、画像ビューの垂直方向の圧縮抵抗が非常に低くなければならないことを意味し、スタックビューを拡張する代わりに、画像ビューを縮小します。さらに、画像ビューのアスペクト比の制約は、垂直方向と水平方向の制約が相互作用するため、レイアウトが複雑になります。つまり、テキストフィールドの水平コンテンツのハグも非常に低くしなければなりません。そうしないと、画像ビューの縮小が妨げられます。どちらの場合も、優先順位を 48 以下の値に設定します。
動的スタックビュー
このレシピは、実行時にスタックに項目を動的に追加および削除する方法を示しています。スタックへのすべての変更はアニメーション化されます。さらに、スタックビューはスクロールビュー内に配置されるため、リストが長すぎてスクリーンに収まらない場合でもリストをスクロールできます。
このレシピは、スタックビューを動的に操作すること、およびスクロールビュー内のスタックビューを操作することを示すことのみを目的としています。実際のアプリでは、このレシピの動作は代わりに UITableView クラスを使用して実装する必要があります。一般に、スクラッチでビルドされたテーブルビュークローンを実装するためだけに動的スタックビューを使用しないでください。代わりに、他の技術を使用して簡単にビルドできない動的なユーザーインターフェイスを作成するためにそれらを使用してください。
ビューと制約
最初のユーザーインターフェイスは非常に単純です。シーンにスクロールビューを配置し、シーン全体を満たすようにサイズを変更します。次に、スタックビューをスクロールビュー内に配置し、項目の追加ボタンをスタックビュー内に配置します。すべての準備が整ったら、以下の制約を設定します。
- Scroll View.Leading = Superview.LeadingMargin
- Scroll View.Trailing = Superview.TrailingMargin
- Scroll View.Top = Superview.TopMargin
- Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
- Stack View.Leading = Scroll View.Leading
- Stack View.Trailing = Scroll View.Trailing
- Stack View.Top = Scroll View.Top
- Stack View.Bottom = Scroll View.Bottom
- Stack View.Width = Scroll View.Width
属性
属性インスペクタで、以下のスタックビュー属性を設定します。
スタック | 軸 | 整列 | 配布 | 行間 |
スタックビュー | 垂直 | 満たす | 均一行間 | 0 |
コード
このレシピでは、項目をスタックビューに追加したり、スタックビューから削除したりするために、少しだけコードが必要です。スクロールビューとスタックビューの両方のアウトレットを使用して、シーンのカスタムビューコントローラを作成します。
1 class DynamicStackViewController: UIViewController {
2
3 @IBOutlet weak private var scrollView: UIScrollView!
4 @IBOutlet weak private var stackView: UIStackView!
5
6 // Method implementations will go here...
7
8 }
次に、viewDidLoad メソッドをオーバーライドして、スクロールビューの初期位置を設定します。スクロールビューのコンテンツがステータスバーの下から始まるようにします。
1 override func viewDidLoad() {
2 super.viewDidLoad()
3
4 // setup scrollview
5 let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
6 scrollView.contentInset = insets
7 scrollView.scrollIndicatorInsets = insets
8
9 }
次に、項目追加ボタンのアクションメソッドを追加します。
1 // MARK: Action Methods
2
3 @IBAction func addEntry(sender: AnyObject) {
4
5 let stack = stackView
6 let index = stack.arrangedSubviews.count - 1
7 let addView = stack.arrangedSubviews[index]
8
9 let scroll = scrollView
10 let offset = CGPoint(x: scroll.contentOffset.x,
11 y: scroll.contentOffset.y + addView.frame.size.height)
12
13 let newView = createEntry()
14 newView.hidden = true
15 stack.insertArrangedSubview(newView, atIndex: index)
16
17 UIView.animateWithDuration(0.25) { () -> Void in
18 newView.hidden = false
19 scroll.contentOffset = offset
20 }
21 }
このメソッドは、スクロールビューの新しいオフセットを計算してから、新しいエントリビューを作成します。エントリビューが非表示に成り、スタックに追加されます。非表示のビューは、スタックの外観やレイアウトに影響を与えないため、スタックの外観は変更されないままです。次に、アニメーションブロックでビューが表示され、スクロールオフセットが更新されて、ビューの外観がアニメーション化されます。
エントリを削除する同様のメソッドを追加します。ただし、addEntry メソッドとは異なり、このメソッドは Interface Builder のどのコントロールにもリンクされていません。代わりに、ビューが作成されると、アプリは各エントリビューをこのメソッドにプログラムでリンクします。
1 func deleteStackView(sender: UIButton) { 2 if let view = sender.superview { 3 UIView.animateWithDuration(0.25, animations: { () -> Void in 4 view.hidden = true 5 }, completion: { (success) -> Void in 6 view.removeFromSuperview() 7 }) 8 } 9 }
このメソッドは、アニメーションブロック内のビューを非表示にします。アニメーションが完了すると、ビューはビュー階層から削除されます。これにより、スタックの配置されたビューのリストからビューが自動的に削除されます。
エントリビューはどのビューでもかまいませんが、この例では、日付ラベル、ランダムな 16 進文字列を含むラベル、削除ボタンを含むスタックビューを使用しています。
1 // MARK: - Private Methods
2 private func createEntry() -> UIView {
3 let date = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .ShortStyle, timeStyle: .NoStyle)
4 let number = "\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())-\(randomHexQuad())"
5
6 let stack = UIStackView()
7 stack.axis = .Horizontal
8 stack.alignment = .FirstBaseline
9 stack.distribution = .Fill
10 stack.spacing = 8
11
12 let dateLabel = UILabel()
13 dateLabel.text = date
14 dateLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
15
16 let numberLabel = UILabel()
17 numberLabel.text = number
18 numberLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
19
20 let deleteButton = UIButton(type: .RoundedRect)
21 deleteButton.setTitle("Delete", forState: .Normal)
22 deleteButton.addTarget(self, action: "deleteStackView:", forControlEvents: .TouchUpInside)
23
24 stack.addArrangedSubview(dateLabel)
25 stack.addArrangedSubview(numberLabel)
26 stack.addArrangedSubview(deleteButton)
27
28 return stack
29 }
30
31 private func randomHexQuad() -> String {
32 return NSString(format: "%X%X%X%X",
33 arc4random() % 16,
34 arc4random() % 16,
35 arc4random() % 16,
36 arc4random() % 16
37 ) as String
38 }
39 }
議論
このレシピが示すように、実行時にスタックビューに対してビューを追加または削除できます。スタックのレイアウトは、配置されたビューの配列に対する変更を補正するために自動的に調整されます。ただし、覚えておくべき重要な点がいくつかあります。
- 非表示のビューは、まだ、配置されたビューのスタックの配列の中にあります。ただし、それらは表示されず、他の配置されたビューのレイアウトには影響しません。
- 配置されたビューのスタックの配列にビューを追加すると、自動的にビュー階層に追加されます。
- 配置されたビューのスタックの配列からビューを削除しても、ビュー階層からは自動的に削除されるわけではありません。ただし、ビュー階層からビューを削除すると、配置されたビュー配列からビューが削除されます。
- iOS では、ビューの hidden (非表示) プロパティは通常アニメーション化できません。ただし、このプロパティは、スタックの配置されたビュー配列に置かれるとすぐに、ビューでアニメーション化可能になります。実際のアニメーションは、ビューではなくスタックによって管理されます。スタックへのビューの追加またはスタックからのビューの削除のアニメーション化には、hidden プロパティを使用します。
このレシピはまた、スクロールビューで自動レイアウトを使用するアイデアも紹介しています。ここでは、スタックとスクロールビューの間の制約により、スクロールビューのコンテンツ領域のサイズが設定されます。等幅制約は、スタック (およびコンテンツサイズ) を明示的に設定して、スクロールビューを水平方向に埋めます。垂直方向では、コンテンツサイズはスタックの埋めるサイズに基づいています。スタックビューは、ユーザーがエントリをもっと追加するにつれて長くなります。コンテンツが多すぎてスクリーンに収まらない場合、スクロールが自動的に有効になります。
詳細については、スクロールビューの操作 を参照してください。
前:Interface Builder での制約の操作 次:簡単な制約