Xcodeの画面を見ていてもよくわからないので、がんばってAutoLayout の概要をおさえる
Storyboardでを使って画面を作成(子画面、TableViewなど)、画面遷移(セグエの利用)させることができたので、画面のレイアウトについて試そうと思ったのだが、キーワードとして出てくるAutoLayoutがググってなかなかわかりやすい記事に当たらない、、、
XCodeで適当に設定してみても、今ひとつ的を得ない。。。
DeppLのお世話になって公式サイトAuto Layout Guide: Understanding Auto Layout (apple.com)の内容を確認して、メモしておく。
長いので概念的なところまで。Interfece Builder の使い方は別途。
AutoLayout
iOSとmacOSはいずれも、Interface Builderによって強力にサポートされるAuto Layoutというパワフルなレイアウトシステムを備えています。Auto Layoutは、インターフェイス内の各オブジェクトが制約を定義し、親ビューや他のインターフェイスコントロールにどう反応するかを管理できる、という考え方を基盤としています。たとえば、異なる言語を表示した時にボタンを特定のサイズで固定するか、大きなテキストに合わせて拡大するかを決めることができます。
Interface Builderでは、すべての制約を自動的に作成することができるため、互換性のあるルールが実現します。また制約を直接コントロールし、それぞれに厳密な優先度を設定することも可能で、異なる画面サイズ、または回転させた際や異なるロケールで実行した場合のAppの動作を定義できます。
- Auto Layout Techniques in Interface Builder – WWDC17 – Videos – Apple Developer
- Auto Layout Guide: Understanding Auto Layout (apple.com)
- Auto Layout Cookbook
- Debugging AutoLayout
- Advanced AutoLayout
制約に基づいた設計を行うことで、内部と外部の両方の変化に動的に対応するユーザーインターフェイスを構築することができます
外形変更
外形変更は、スーパービューのサイズや形状が変更されたときに発生します。変更のたびに、利用可能なスペースを最適に使用するために、ビュー階層のレイアウトを更新する必要があります
内部変更
内部変更は、ユーザーインターフェイスのビューやコントロールのサイズが変更されたときに発生します
自動レイアウトとフレームベースレイアウトの比較
ユーザーインターフェースのレイアウトには、主に3つのアプローチがあります。
- プログラムによってユーザーインターフェースをレイアウト
- オートレジシングマスクを使って外部からの変化への対応を自動化
- そしてAuto Layoutを使う
従来、アプリは、ビュー階層内の各ビューのフレームをプログラムで設定することで、ユーザーインターフェイスをレイアウトしていました。フレームは、スーパービューの座標系でビューの原点、高さ、幅を定義しました。
プログラムによってビューのフレームを定義することは、多くの点で最も柔軟で強力な方法ですが、すべての変更を自分で管理しなければならないため、保守にかなりの労力を必要とし難易度は桁違いに高くなる。
オートサイジングマスク
オートサイジングマスクを使用することで、この労力を軽減することができます。スーパービューのフレームが変更されたときに、ビューのフレームがどのように変更されるかを定義し、外部からの変更に適応するレイアウトの作成が簡単となる
複雑なユーザーインターフェースでは、一般的に、独自のプログラムによる変更でオートサイジングマスクを補強する必要があります。さらに、オートサイジングマスクは外部からの変更にのみ適応します。内部の変更には対応しません。
Auto Layout
Auto Layoutはビューのフレームを考える代わりに、そのリレーションシップを考える
Auto Layoutは、一連の制約を使用してユーザーインターフェイスを定義します。制約は、通常、2つのビュー間の関係を表します。Auto Layoutは、これらの制約に基づいて、各ビューのサイズと位置を計算します。これにより、内部と外部の両方の変化に動的に対応するレイアウトが作成されます
制約のないオートレイアウト
スタックビュー
- 複雑な制約を設けることなく、簡単にオートレイアウトの機能を活用できる
- 1つのスタックビューは、ユーザインタフェース要素の行または列を定義
- プロパティに基づいてこれらの要素を配置します。
- オブジェクトが固有のコンテンツサイズを持っている場合、そのサイズでスタックに表示されます。
- コンテンツに固有のサイズがない場合、Interface Builder はデフォルトのサイズを提供します。
- オブジェクトのサイズを変更すると、Interface Builder はそのサイズを維持するための制約を追加します。
- レイアウトをさらに細かく調整するには、属性インスペクタを使ってスタックビューのプロパティを変更します。
axis | (UIStackViewのみ) スタックビューの方向を定義し、垂直または水平のどちらかを指定します |
orientation | スタックビューの方向を指定します。(NSStackView のみ) スタックビューの方向を、垂直または水平に定義 |
distribution | 軸に沿ったビューのレイアウトを定義 |
alignment | スタックビューの軸に垂直なビューのレイアウトを定義 |
spacing | 隣接するビューの間のスペースを定義します。 |
一般に、レイアウトの管理にはできるだけスタックビューを使用します。スタックビューだけでは目標を達成できない場合にのみ、制約を作成するようにしてください
制約の構造
ビュー階層のレイアウトは、一連の線形方程式として定義されます。各制約は1つの方程式を表します
Item1 | 式中の最初の項目、この場合は赤いビュー。この項目は、ビューまたはレイアウトガイドのいずれかでなければならない。 |
Attribute1 | 最初の項目で制約される属性(この場合、赤のビューのリーディングエッジ)。 |
Relationship | 左側と右側の関係。関係は、3つの値(等しい、等しいより大きい、等しいより小さい)のうちの1つを持つことができます。この場合、左辺と右辺は等しくなる。 |
Multiplier | 属性2の値にこの浮動小数点数を乗じる。この場合、乗算は1.0である。 |
Item2 | 式の2番目の項目。この場合はブルービュー。最初の項目とは異なり、空白にすることができる。 |
Attribute2 | 2番目の項目で制約される属性(この場合、青いビューの後縁)。2番目の項目が空白の場合、これはNot an Attributeでなければならない。 |
Constant | 定数、浮動小数点オフセット-この場合、8.0。この値は、属性2の値に追加される。 |
- ほとんどの制約は、ユーザーインターフェースの2つのアイテムの関係を定義
- ビューまたはレイアウトガイドを表すことができます。
- 制約は1つのアイテムの2つの異なる属性間の関係を定義することもできます。
- アイテムの高さと幅の間のアスペクト比を設定することができます。
- アイテムの高さや幅に定数値を割り当てることもできます。
- 定数値を使用する場合、2つ目の項目は空白のまま、2つ目の属性は「Not An Attribute」に設定し、倍率は「0.0」に設定されます。
オートレイアウトの属性
- オートレイアウトでは、属性が制約を受ける可能性のあるフィーチャーを定義します。
- 一般的には、4つのエッジ(先行、後行、上、下)、高さ、幅、縦と横の中心が含まれます。
- テキストアイテムは1つ以上のベースライン属性も持っています。
属性の完全なリストについては、NSLayoutAttribute enumを参照
曖昧でないレイアウトの作成
- 一般に、制約は各ビューのサイズと位置の両方を定義する必要があります。
- スーパービューのサイズがすでに設定されていると仮定すると曖昧でない満足できるレイアウトには、1次元につきビューごとに2つの制約が必要です(スーパービューは考慮しない)
- 使用する制約の選択については、幅広いオプションがあります。
- 次の3つのレイアウトは、すべて非曖昧性を満たすレイアウトを生成します(水平方向の制約のみ表示されます)
- 最初のレイアウトは、ビューのリーディングエッジをスーパービューのリーディングエッジと相対的に制約するものです。また、ビューの幅も固定されています。後縁の位置は、スーパービューのサイズと他の制約に基づいて計算することができます。
- 2つ目のレイアウトは、ビューの前縁をスーパービューの前縁に相対的に制約します。また、ビューの後縁をスーパー ビューの後縁に相対的に制約します。そして、ビューの幅は、スーパービューのサイズと他の制約に基づいて計算することができます。
- 3番目のレイアウトは、ビューの前縁をスーパー ビューの前縁に相対的に拘束します。また、ビューとスーパービューのセンターアラインメントを行います。そして、幅と後縁の位置の両方が、スーパービューのサイズと他の制約に基づいて計算されることができます。
各レイアウトには1つのビューと2つの水平方向の制約があり、それぞれ制約はビューの幅と水平位置の両方を完全に定義してる。
すべてのレイアウトは、水平軸に沿って曖昧でない、満足できるレイアウトを生成する。しかし、これらのレイアウトは等しく有用というわけではありません。
最初のレイアウトでは、ビューの幅は変わりません。たいていの場合、これは望むところではありません。ビューに固定サイズを与えると、その能力を短絡させてしまうことになります。
2番目と3番目のレイアウトは同じ動作をしています。どちらも、スーパービューの幅が変化しても、ビューとそのスーパービューの間に一定のマージンが維持されます。
しかし、両者は必ずしも同じではありません。一般的には、2番目の例の方が理解しやすいと思いますが、特に多くのアイテムをセンターアラインする場合は、3番目の例の方が便利かもしれません。
制約条件付き不等式
約は不等式も表すことができます。具体的には、制約の関係は、等しい、等しいより大きい、等しいより小さい、にすることができます。
たとえば、ビューの最小サイズと最大サイズを定義するために制約を使うことができます
1つの等式関係を2つの不等式で置き換えることができます。
// A single equal relationship Blue.leading = 1.0 * Red.trailing + 8.0 // Can be replaced with two inequality relationships Blue.leading >= 1.0 * Red.trailing + 8.0 Blue.leading <= 1.0 * Red.trailing + 8.0
制約の優先順位
オプションの制約を作成することもできます。すべての制約の優先順位は1〜1000です。優先度1000の制約は必須です。その他の制約は任意です。
解を計算するとき、Auto Layout は、優先順位の高いものから低いものへと、すべての制約を満たそうとします。オプションの制約を満たすことができない場合、その制約はスキップされ、次の制約に進みます
優先度はシステムで定義された低(250)、中(500)、高(750)、要(1000)優先度のあたりにまとめるのが一般的でしょう
iOSの定義済み制約定数の一覧は、UILayoutPriority列挙型を参照してください。OS X では、Layout Priorities 定数を参照
本質的なコンテンツサイズ
いくつかのビューは、その現在のコンテンツから自然なサイズを持っています。これは、その本質的なコンテンツのサイズと呼ばれます。例えば、ボタンの固有コンテンツサイズは、そのタイトルのサイズに小さな余白を加えたものです。
View | 本質的なコンテンツサイズ |
UIView and NSView | 本質的なコンテンツサイズがない。 |
スライダー | 幅だけを定義する(iOS)スライダーの種類に応じて、幅、高さ、またはその両方を定義します(OS X) |
ベル、ボタン、スイッチ、テキスト・フィールド | 高さと幅の両方を定義 |
テキストビューとイメージビュー | 本質的なコンテンツサイズは変化する可能性 |
これらの制約は、不等式を使用して定義されています。ここで、IntrinsicHeight と IntrinsicWidth 定数は、ビューの内在するコンテンツサイズからの高さと幅の値を表します。
// Compression Resistance View.height >= 0.0 * NotAnAttribute + IntrinsicHeight View.width >= 0.0 * NotAnAttribute + IntrinsicWidth // Content Hugging View.height <= 0.0 * NotAnAttribute + IntrinsicHeight View.width <= 0.0 * NotAnAttribute + IntrinsicWidth
これらの制約には、それぞれ優先順位があります。デフォルトでは、ビューは、コンテンツ抱擁のために250の優先度を使用し、圧縮抵抗のために750の優先度を使用します。したがって、ビューを伸ばすことは、縮小することよりも簡単です。ほとんどのコントロールで、これは望ましい動作です。例えば、ボタンをその固有のコンテンツサイズより大きく引き伸ばしても安全ですが、縮小するとコンテンツが切り取られる可能性があります
よくある例として、ラベルとテキストフィールドのペアがあります。通常、ラベルは本来のコンテンツ・サイズのまま、テキスト・フィールドは余分なスペースを埋めるように伸縮させたいものです。これを確実にするために、テキストフィールドの水平方向のコンテンツ保持の優先度をラベルの優先度より低くします。
実際、この例はよくあることなので、Interface Builderは自動的にこの処理を行い、すべてのラベルのコンテンツハギングプライオリティを251に設定します。もし、プログラムでレイアウトを作成する場合は、コンテンツハギングプライオリティを自分で変更する必要があります。