Auto Layoutは、数学的制約(constraints)に基づくAppleのアダプティブインターフェース要素配置システムです。iOS 6(2012年)向けに開発されたAuto Layoutは、iPhone SE(4.7″)からiPad Pro(12.9″)やDynamic Islandまで、すべてのデバイスで正しく表示されるインターフェースを作成できます。Apple WWDC Session 202(2024年)によると、App Storeのアプリの90%以上がAuto Layoutまたはその宣言型代替手段であるSwiftUI layout systemを使用しています。Constraintsは線形方程式を通じてUI要素間の依存関係を記述します:view1.leading = view2.trailing + 8。
重要なポイント
Auto Layoutは、UI要素を配置するために数学的制約(constraints)を使用するAppleのアダプティブレイアウトシステムです。各要素が固定座標x、y、width、heightを持つフレームベースのレイアウトとは異なり、Auto Layoutは要素間の関係を記述します:「ボタンは親の右端から8ptの位置にある」または「テキストフィールドの幅は画面幅の半分に等しい」。このメカニズムは、University of Washington(Greg J. Badros、1999年)で開発されAppleがiOS 6に実装したCassowaryアルゴリズムに基づいています。Cassowaryは優先順位(Required(1000)、Default High(750)、Default Low(250))を持つ線形不等式システムを解決し、制約の競合を管理できます。Auto Layoutは3つのサイズタイプをサポートします:intrinsic(コンテンツによって決定される要素の自然なサイズ)、explicit(明示的に設定された制約)、compressible/stretchable(Content Hugging PriorityとCompression Resistance Priorityによる伸縮モード)。
Auto Layoutの各UI要素にはIntrinsic Content Size(そのコンテンツによって決定される自然なサイズ)があります。UILabelの場合、これはテキストとフォントに依存し、UIImageViewの場合は画像の寸法に依存します。Content Hugging Priority(引き伸ばしへの耐性)とCompression Resistance Priority(圧縮への耐性)は、利用可能なスペースが変化したときの要素の動作を制御します。標準値はhuggingが251、compression resistanceが749です。2つの要素がスペースを競合する場合、優先順位によってどちらが先に引き伸ばされるかが決まります。これらの優先順位を理解することは、Xcodeがデバッガで強調表示するAmbiguous Layout(あいまいなレイアウト)を解決する鍵です。
制約は次の方程式で記述されます:view1.attribute = multiplier × view2.attribute + constant。属性にはleading、trailing、top、bottom、centerX、centerY、width、height、firstBaseline、lastBaselineが含まれます。Multiplierは比例関係に使用されます(view1の幅 = 0.5 × superviewの幅)。Constantは固定オフセットを設定します(leading = superview.leading + 16)。Interface BuilderツールではCtrlドラッグで視覚的に制約を作成できますが、複雑なレイアウトではNSLayoutConstraintまたはVFL(Visual Format Language)によるプログラムでの作成が必要であり、AppleはiOS 9以降NSLayoutConstraintへの置き換えを推奨しています。
制約システムは線形計画問題として解決されます:Cassowaryアルゴリズムは、優先順位を考慮してすべての制約を満たすすべての要素の最適な配置を見つけます。制約が互いに矛盾する場合、Unsatisfiable Layoutが発生します — Xcodeが詳細な競合説明とともにログする例外です。少なくとも1つの要素の位置を決定するのに十分な制約がない場合、Ambiguous Layoutが発生し、要素は任意の位置に表示されます。Appleは最小限のセットを推奨します:各要素について、position(x、y)とsize(width、height)を明示的またはintrinsic content sizeを通じて設定する必要があります。制約は第一級であり、リーディング要素(例:superview)とトレーリング要素(子ビュー)が階層を作成します。
CassowaryはSequential Quadratic Programming法を使用して線形不等式システムを解決します。各制約には1〜1000の優先順位があります。Required(1000)は必須の制約であり、満たせない場合はアプリがNSConstraintExceptionでクラッシュします。Default High(750)は推奨、Default Low(250)は最も重要度が低いです。競合時、Cassowaryは低い優先順位の制約を緩和します。たとえば、2つの要素が固定幅を必要とするが画面が狭すぎる場合、低い優先順位の制約が緩和されます。Xcode Debug View Hierarchy(Xcode 6以降で利用可能なデバッグツール)では、Required制約の問題のみが強調表示され、その他はエラーなく処理されます。
UIStackViewはiOS 9(2015年)で導入されたコンテナで、ネストされたarrangedSubviewsの制約を自動的に作成および管理します。UIStackViewは2つの軸(水平と垂直)をサポートします。distribution設定はスペース配分を決定します:fill(hugging priorityに比例した充填)、fillEqually(均等サイズ)、fillProportionally(intrinsic content sizeに比例)、equalSpacing(均等間隔)、equalCentering(中心間の等距離)。Alignmentは交差軸の整列を定義します:fill、leading、center、trailing(水平の場合)またはfill、top、center、bottom(垂直の場合)。UIStackViewは間隔、ベースライン整列、Dynamic Typeへの適応を自動的に管理します。
import UIKit
class StackViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stack = UIStackView()
stack.axis = NSLayoutConstraint.Axis.vertical
stack.distribution = .fillEqually
stack.spacing = 8
stack.translatesAutoresizingMaskIntoConstraints = false
let label = UILabel()
label.text = "Auto Layout Guide"
label.font = UIFont.preferredFont(forTextStyle: .headline)
let button = UIButton(type: .system)
button.setTitle("Apply", for: .normal)
stack.addArrangedSubview(label)
stack.addArrangedSubview(button)
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stack.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 16),
stack.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -16)
])
}
}コードは2つの要素(UILabelとUIButton)を持つ垂直のUIStackViewを作成し、8ptの間隔で均等に分配(fillEqually)します。スタックは端から少なくとも16ptのマージンで画面中央に配置されます。translatesAutoresizingMaskIntoConstraints = falseはプログラムで制約を作成する際に必須です — これがないとAuto Layoutは機能しません。IT Sectrでは、UIStackViewはiOSプロジェクトの画面の80%でアダプティブフォーム、設定リスト、カードの構築に使用されています。
UIStackViewはネスト可能です:垂直スタック内の水平スタックは複雑なレイアウトの標準的なパターンです。外部スタックは行を管理し、内部スタックは各行内の列を管理します。各レベルでのaxis、alignment、distributionの組み合わせにより、1つの手動制約なしで事実上無制限の柔軟性が得られます。AppleはUIKitの主要なレイアウトツールとしてUIStackViewを推奨し、スタックでカバーされないケース(オーバーラップするビュー、正確なピクセル配置、カスタムboundsアニメーション)にのみ手動のNSLayoutConstraintに頼ることを推奨しています。
NSLayoutConstraintは、コード内で個別の制約を作成するためのプログラム用APIです。各制約は、item、attribute、relatedBy、toItem、attribute、multiplier、constantのパラメータを持つイニシャライザを介して作成されます。iOS 9以降、AppleはAnchor APIを導入しました — view.leadingAnchor、view.trailingAnchor、view.topAnchor、view.bottomAnchor、view.centerXAnchor、view.centerYAnchor、view.widthAnchor、view.heightAnchorプロパティを通じたより読みやすい構文です。Anchor APIは自動的にrelatedBy = .equalを設定し、AnchorsからFirst Item/Second Itemを使用するため、従来のNSLayoutConstraintと比較してコードが40%削減されます。
import UIKit
class ConstraintViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let childView = UIView()
childView.backgroundColor = .systemBlue
childView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(childView)
NSLayoutConstraint.activate([
childView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 24),
childView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
childView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
childView.heightAnchor.constraint(equalToConstant: 120),
childView.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -24)
])
}
}コードはchildViewをsafeAreaLayoutGuide(top)と画面端(leading/trailing)からのマージンで配置します。bottomのlessThanOrEqualToは、ビューが下端を超えないことを保証します。Anchor APIはアンカーが互換性がない場合(例:leadingAnchorとrightAnchorの混在)、コンパイル時に例外をスローし、実行時エラーを防ぎます。AppleはiOS 9以降のプログラムによるAuto Layoutの標準としてAnchor APIを推奨しています。
Safe Areaは、Dynamic Island、Notch、Status Bar、Home Indicator、丸みを帯びた角などのシステム要素で覆われていない画面領域です。iOS 11でAppleはtopLayoutGuide/bottomLayoutGuideをsafeAreaLayoutGuideに置き換え、デバイスの向きや画面の切り欠きの有無に自動的に適応します。Layout Marginsはビューのデフォルトの内部マージンです(iOSで16pt、iPadOSで20pt)。UILayoutGuideでは、RIGHT-TO-LEFTローカリゼーションを考慮したカスタムdirectionalLayoutMarginsを設定できます。Auto LayoutはアンカーでsafeAreaLayoutGuideを使用する際に自動的にsafe areaを尊重します。
Dynamic Island(iPhone 14 Pro以降)およびNotch(iPhone X〜13)を搭載したデバイスでは、Safe Areaはポートレートで上部44pt(アクティブ状態のDynamic Islandでは59pt)を除外します。Home Indicatorは下部に34ptを追加します。正しく適応するには、すべての上部制約をsafeAreaLayoutGuide.topAnchorに、view.topAnchorではなく接続する必要があります。下部制約はsafeAreaLayoutGuide.bottomAnchorまたはHome Indicator用のマージンを含むview.bottomAnchorに接続する必要があります。IT Sectrでは、すべての画面をiPhone SE(2022)、iPhone 14 Pro Max、iPad Pro 12.9″のシミュレーターでテストしています — すべてのsafe areaバリエーションをカバーする3つのデバイスです。
Auto Layoutを使用する際の最も頻繁なエラー:translatesAutoresizingMaskIntoConstraints = falseの忘れ、競合するRequired制約(優先順位1000)、Ambiguous Layout(位置決定に不十分な制約)、複数行UILabelの誤ったContent Hugging Priority、leading/trailingとleft/rightアンカーの混在です。Xcode 15+はRuntime Issue Navigatorでレイアウト問題を表示し、自動修正を提案します。複雑なレイアウトにはDebug View Hierarchyを使用してください:黄色いマーカーはambiguous layoutを、赤いマーカーはunsatisfiableを示します。
| エラー | 原因 | 解決策 |
|---|---|---|
| translatesAutoresizingMaskIntoConstraints = true | ビューに対してAuto Layoutが有効化されていない | すべてのプログラムビューでfalseに設定 |
| Unsatisfiable Layout | Required(1000)制約の競合 | 一方の優先順位をDefault High(750)に下げる |
| Ambiguous Layout | x/y/w/hに対する不十分な制約 | 不足している制約を追加するかintrinsic sizeを確認 |
| UILabelでのテキスト切り詰め | Content Hugging Priorityが競合より低い | hugging priorityを252+に上げる |
| LTR/RTLアンカーの混在 | leadingAnchorとrightAnchor | RTLサポートにはleading/trailingのみを使用 |
よくある質問
フレームベースのレイアウトは各要素に固定座標x、y、width、heightを設定します。Auto Layoutは数学的制約(constraints)— 要素間の関係を使用します:「label.leading = button.trailing + 8」。フレームベースのレイアウトは画面サイズに適応しませんが、Auto Layoutは回転、Split View、Dynamic Typeの変更時に自動的に位置を再計算します。
UIStackViewは線形レイアウト(行、列、フォーム、パラメータリスト)に最適です。NSLayoutConstraintは、オーバーラップするビュー、正確なピクセル配置、カスタムboundsアニメーション、およびスペース配分が不均一でUIStackViewのdistributionでカバーされない場合に必要です。実際には、80%のレイアウトはUIStackViewで解決され、20%は手動の制約で解決されます。
Content Hugging Priority(引き伸ばしへの耐性)は、要素がIntrinsic Content Sizeを超えてサイズが増加することにどの程度抵抗するかを決定する優先順位です。デフォルト値は251です。2つの要素が空きスペースを競合する場合、hugging priorityが高い要素はそのサイズを維持し、もう一方が引き伸ばされます。Compression Resistance Priority(デフォルト749)は圧縮に対して同様に機能します。
Auto Layoutは、制約がラベルのintrinsic content sizeを使用する場合、Dynamic Typeに自動的に適応します。フォントサイズが大きくなるとUILabelが拡張し、制約を通じて隣接する要素を移動させます。distribution = fillProportionallyのUIStackViewは、新しいintrinsic sizeに比例してスペースを再分配します。Safe AreaとLayout Marginsもアクセシビリティ設定を尊重します。
Unsatisfiable Layoutは、2つのRequired(priority = 1000)制約が互いに矛盾する場合に発生します:たとえば、view.leading = superview.leading + 16とview.trailing = superview.leading + 200で、superviewの幅が100ptの場合です。Cassowaryアルゴリズムは解を見つけられず、アプリはNSConstraintExceptionでクラッシュします。解決策は、競合する制約の一方の優先順位をDefault High(750)に下げることです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。