axiom-auto-layout-debugging について
Auto Layoutを扱うiOS開発者向けのデバッグスキルです。Xcodeのツールで競合するビューや制約を特定し、よくあるレイアウト問題を調べる手順をまとめています。
主な機能
- 01シンボリックブレークポイントで競合を特定
- 02メモリアドレスからビューを識別
- 03ビュー階層と制約を視覚的に確認
- 04曖昧なレイアウトと優先度を分析
- 05デバッグ用に制約とビューへラベル付け
活用シーン
- 01コンソールの制約警告を調査
- 02実行時に位置やサイズがずれるビューを特定
- 03StoryboardやXIBのレイアウト問題を診断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-auto-layout-debuggingインストール後、Claude Code で/axiom-auto-layout-debuggingを入力して読み込まれたか確認します。
詳細
Auto Layout のデバッグ
このスキルを使用するタイミング
次の場合に使用します:
- コンソールに「Unable to simultaneously satisfy constraints」エラーが表示される
- ビューの位置が正しくない、または表示されない
- アプリの起動時や画面遷移時に制約の警告が表示される
- あいまいなレイアウトエラーが発生する
- ビューが想定外のサイズで表示される
- Debug View Hierarchy でビューの位置ずれが確認される
- Storyboard/XIB の制約の動作が実行時に異なる
概要
基本原則:Auto Layout の制約エラーには、予測可能なパターンがあります。適切なツールを使って体系的にデバッグすれば、数時間ではなく数分で問題を特定できます。
時間の節約:このワークフローを使わずに制約をデバッグする場合、通常は 30〜60 分かかります。体系的な方法なら 5〜10 分で済みます。
クイック判定フロー
Constraint error in console?
├─ Can't identify which views?
│ └─ Use Symbolic Breakpoint + Memory Address Identification
├─ Constraint conflicts shown?
│ └─ Use Constraint Priority Resolution
├─ Ambiguous layout (multiple solutions)?
│ └─ Use _autolayoutTrace to find missing constraints
└─ Views positioned incorrectly but no errors?
└─ Use Debug View Hierarchy + Show Constraints
制約エラーメッセージを理解する
エラーメッセージの構成
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list you don't need.
(
"<NSLayoutConstraint:0x7f8b9c6... 'UIView-Encapsulated-Layout-Width' ... (active)>",
"<NSLayoutConstraint:0x7f8b9c5... UILabel:0x7f8b9c4... .width == 300 (active)>",
"<NSLayoutConstraint:0x7f8b9c3... UILabel:0x7f8b9c4... .leading == ... + 20 (active)>",
"<NSLayoutConstraint:0x7f8b9c2... ... .trailing == UILabel:0x7f8b9c4... .trailing + 20 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7f8b9c5... UILabel:0x7f8b9c4... .width == 300 (active)>
主な構成要素:
- メモリアドレス —
0x7f8b9c4...はビューと制約を識別します - Visual Format — 制約を人が読める形式で説明します
(active)ステータス — 制約が現在適用されていることを示します- 復旧時の動作 — どの制約システムが制約を破棄するかを示します(通常は優先度が最も低いものです)
システム生成の制約
UIView-Encapsulated-Layout-Width/Height:
- UIKit がセルやシステムビュー用に作成します
- 多くの場合、競合の原因になります
- 通常は正しく、問題は独自の制約にあります
Autoresizing Mask の制約:
- 形式:
h=--&またはv=&-- -= 固定サイズ&= 可変サイズ- 例:
h=--&= 左マージンと幅が固定され、右マージンが可変
デバッグのワークフロー
ステップ 1:シンボリックブレークポイントを設定する(初回のみ)
目的:制約の競合が発生したとき、システムが制約を破棄する前に実行を中断します。
設定方法:
- Breakpoint Navigator を開きます(⌘+7 または ⌘+8)
+→「Symbolic Breakpoint」をクリックします- Symbol:
UIViewAlertForUnsatisfiableConstraints - (任意)Action を追加 →「Sound」→ サウンドを選択します
- (任意)「Automatically continue after evaluating actions」にチェックを入れます
この方法が有効な理由:制約の競合が発生した瞬間に実行を一時停止するため、デバッガーからすべてのビューと制約を確認できます。
ステップ 2:メモリアドレスからビューを特定する
ブレークポイントに到達すると、コンソールに UILabel:0x7f8b9c4... のようなメモリアドレスが表示されます。
方法 1:%rbx レジスターを使う(ブレークポイントに到達したとき)
# Print all involved views and constraints
po $arg1
# Or on older Xcode versions
po $rbx
出力:競合するすべての制約と、影響を受けるビューを含む NSArray です。
方法 2:ビューの背景色を設定する
# Set background color on suspected view
expr ((UIView *)0x7f8b9c4...).backgroundColor = [UIColor redColor]
# Continue execution to see which view turned red
結果:メモリアドレスに対応するビューを視覚的に特定できます。
方法 3:ビュー階層を出力する
Objective-C プロジェクト:
po [[UIWindow keyWindow] _autolayoutTrace]
Swift プロジェクト:
expr -l objc++ -O -- [[UIWindow keyWindow] _autolayoutTrace]
出力:あいまいなレイアウトに * を付けた、ビュー階層全体です。
例:
*<UIView:0x7f8b9c4...>
| <UILabel:0x7f8b9c3...>
* は、この UIView にあいまいな制約があることを示します。
方法 4:特定のビューの制約を出力する
# Horizontal constraints (axis: 0)
po [0x7f8b9c4... constraintsAffectingLayoutForAxis:0]
# Vertical constraints (axis: 1)
po [0x7f8b9c4... constraintsAffectingLayoutForAxis:1]
出力:そのビューのレイアウトに影響するすべての制約です。
ステップ 3:Debug View Hierarchy を使う
使用するタイミング:ビューの位置が正しくない場合や、コード内で制約を確認できない場合です。
ワークフロー:
- 問題を再現する — 制約に問題がある画面に移動します
- 実行を一時停止する — デバッグバーの「Debug View Hierarchy」ボタンをクリックします(または Debug → View Debugging → Capture View Hierarchy を選択します)
- 3D ビューを調べる — ビュー階層を回転させてレイヤーを確認します
- 「Show Constraints」を有効にする — すべての制約を線で表示します
- ビューを選択する — 右側のパネルに、選択したビューに影響するすべての制約が表示されます
主な機能:
- Show Clipped Content — 画面外に配置されたビューを確認できます
- Show Constraints — 制約の関係を視覚化します
- Filter Bar — クラス名やメモリアドレスで特定のビューを検索します
問題の特定:
- 紫色の制約 = 制約を満たしています
- オレンジ色/赤色の制約 = 競合しています
- 制約を選択 → 接続されている両方のビューを確認できます
ステップ 4:制約に名前を付ける(予防策)
理由:エラーメッセージが、解読しにくいメモリアドレスではなく、読みやすくなります。
Interface Builder の場合(Storyboard/XIB)
- ドキュメントアウトラインで制約を選択します
- アトリビュートインスペクタを開きます
- Identifier フィールドを設定します(例: 「ProfileImageWidthConstraint」)
変更前:
<NSLayoutConstraint:0x7f8b9c5... UILabel:0x7f8b9c4... .width == 300 (active)>
変更後:
<NSLayoutConstraint:0x7f8b9c5... 'ProfileImageWidthConstraint' UILabel:0x7f8b9c4... .width == 300 (active)>
プログラムによる設定
let widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 100)
widthConstraint.identifier = "ProfileImageWidthConstraint"
widthConstraint.isActive = true
効果: コードを探し回ることなく、どの制約が破られているかを即座に把握できます。
ステップ 5: ビューに名前を付ける(予防策)
理由: エラーメッセージにビュークラスとカスタムラベルの両方が表示されます。
Interface Builder の場合
- ドキュメントアウトラインでビューを選択します
- アイデンティティインスペクタを開きます
- Label フィールドを設定します(例: 「Profile Image View」)
変更前:
<UIImageView:0x7f8b9c4... (active)>
変更後:
<UIImageView:0x7f8b9c4... 'Profile Image View' (active)>
プログラムによる設定
imageView.accessibilityIdentifier = "ProfileImageView"
注記: Xcode は利用可能な場合、テキストコンポーネント(UILabel のテキスト、UIButton のタイトル)を識別子として自動的に使用します。
よくある制約の競合パターン
パターン 1: 固定幅の競合
症状:
Container width: 375
Child width: 300
Child leading: 20
Child trailing: 20
// 20 + 300 + 20 = 340 ≠ 375
❌ 誤り:
// Conflicting constraints
imageView.widthAnchor.constraint(equalToConstant: 300).isActive = true
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
// Over-constrained: width + leading + trailing = 3 horizontal constraints (only need 2)
✅ 正しい方法 1(固定幅を削除する):
// Let width be calculated from leading + trailing
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
// Width will be container width - 40
✅ 正しい方法 2(優先度を使用する):
let widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 300)
widthConstraint.priority = .defaultHigh // 750 (can be broken if needed)
widthConstraint.isActive = true
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true
// Required constraints (1000) will break lower-priority width constraint if needed
パターン 2: UIView-Encapsulated-Layout の競合
症状: テーブルセルまたはコレクションビューセルが UIView-Encapsulated-Layout-Width と競合します。
発生する理由: システムがテーブル/コレクションビューに基づいてセルの幅を設定します。あなたの制約がそれと衝突します。
❌ 誤り:
// In UITableViewCell
contentLabel.widthAnchor.constraint(equalToConstant: 320).isActive = true
// Conflicts with system-determined cell width
✅ 正しい方法:
// Use relative constraints, not fixed widths
contentLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16).isActive = true
contentLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16).isActive = true
// Width adapts to cell width automatically
パターン 3: Autoresizing Mask の競合
症状: Auto Layout と autoresizingMask を併用している、または translatesAutoresizingMaskIntoConstraints = false を設定していない。
❌ 誤り:
let imageView = UIImageView()
view.addSubview(imageView)
// Forgot to disable autoresizing mask
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
// Conflicts with autoresizing mask constraints
✅ 正しい方法:
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false // ← CRITICAL
view.addSubview(imageView)
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
理由: translatesAutoresizingMaskIntoConstraints = true は自動制約を生成し、明示的な制約と競合します。
パターン 4: 曖昧なレイアウト(制約の不足)
症状: ビューは表示されますが、位置が予期せずずれる、または _autolayoutTrace に *(曖昧)が表示されます。
問題: 一意の位置/サイズを決定するのに十分な制約がありません。
❌ 誤り(X 位置が曖昧):
imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// Missing: horizontal position (leading/trailing/centerX)
✅ 正しい方法:
imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true // ← Added
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 100).isActive = true
ルール: すべてのビューには次のものが必要です:
- 水平方向: 2 つの制約(例: leading + width、または leading + trailing、または centerX + width)
- 垂直方向: 2 つの制約(例: top + height、または top + bottom、または centerY + height)
パターン 5: 優先度の競合
症状: 予期しない制約の破壊が発生しますが、すべての制約は正しいように見えます。
問題: 同じ優先度の複数の制約が競合しています。
❌ 誤り:
// Both required (priority 1000)
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true
imageView.widthAnchor.constraint(greaterThanOrEqualToConstant: 150).isActive = true
// Impossible: width can't be 100 AND >= 150
✅ 正しい方法:
let preferredWidth = imageView.widthAnchor.constraint(equalToConstant: 100)
preferredWidth.priority = .defaultHigh // 750
preferredWidth.isActive = true
let minWidth = imageView.widthAnchor.constraint(greaterThanOrEqualToConstant: 150)
minWidth.priority = .required // 1000
minWidth.isActive = true
// Result: width will be 150 (required constraint wins)
優先度レベル(値が大きいほど強い):
.required(1000)— 必ず満たす必要があります.defaultHigh(750)— 強い優先.defaultLow(250)— 弱い優先- カスタム: 1~999 の任意の値
デバッグチェックリスト
デバッグ前
- コンソールの完全なエラーメッセージを読む(無視しない)
- 競合としてリストされている制約を確認する
- エラーが一貫しているか、断続的かを確認する
デバッグ中
- UIViewAlertForUnsatisfiableConstraints にシンボリックブレークポイントを設定する
- メモリアドレスを使用してビューを特定する(背景色テクニック)
- Debug View Hierarchy を使用して制約を可視化する
- 曖昧なレイアウトがないか _autolayoutTrace を確認する
- プログラムで作成したビューで translatesAutoresizingMaskIntoConstraints = false を検証する
修正後
- 複数のデバイスサイズでテストする(iPhone SE、iPhone Pro Max)
- 向きの変更をテストする(縦向き/横向き)
- Dynamic Type のサイズでテストする
- 遷移中にコンソール警告が出ないことを確認する
- 将来のデバッグのために制約識別子を追加する
高度なテクニック
制約の優先度戦略
ユースケース: 特定のサイズにしたいが、必要に応じて縮小できるビュー。
// Preferred size: 200x200
let widthConstraint = imageView.widthAnchor.constraint(equalToConstant: 200)
widthConstraint.priority = .defaultHigh // 750
widthConstraint.isActive = true
let heightConstraint = imageView.heightAnchor.constraint(equalToConstant: 200)
heightConstraint.priority = .defaultHigh // 750
heightConstraint.isActive = true
// But never smaller than 100x100
imageView.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
imageView.heightAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
// And never larger than container
imageView.widthAnchor.constraint(lessThanOrEqualTo: containerView.widthAnchor).isActive = true
imageView.heightAnchor.constraint(lessThanOrEqualTo: containerView.heightAnchor).isActive = true
結果: スペースがあれば画像は 200x200 になり、コンテナに合わせて縮小します(最小 100x100)。
Content Hugging と Compression Resistance
Content Hugging(拡大に抵抗):
// Label should not stretch beyond its text width
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
Compression Resistance(縮小に抵抗):
// Label should not truncate if possible
label.setContentCompressionResistancePriority(.required, for: .horizontal)
よくあるパターン:
// In horizontal stack: priorityLabel (hugs) + spacer + valueLabel (hugs)
priorityLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
valueLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
// Spacer fills remaining space (low hugging priority)
spacerView.setContentHuggingPriority(.defaultLow, for: .horizontal)
変換されたビューのデバッグ
問題:ビューの変換(回転、拡大縮小)が Auto Layout に反映されません。
注意点:
imageView.transform = CGAffineTransform(rotationAngle: .pi / 4) // 45° rotation
// Auto Layout still uses original (un-rotated) frame for calculations
解決策:Auto Layout は正しく動作していますが、視覚的なデバッグが分かりにくい場合があります。制約のデバッグには元のフレームを使用してください。
トラブルシューティング
問題:ブレークポイントに到達しない
確認事項:
- シンボリックブレークポイントのシンボルが正確に
UIViewAlertForUnsatisfiableConstraintsであること - ブレークポイントが有効になっていること(チェックマークが表示されていること)
- 実際に制約の競合が発生していること(コンソールでエラーメッセージを確認してください)
問題:メモリアドレスからビューを特定できない
解決策 1:背景色の手法を使用する
expr ((UIView *)0x7f8b9c4...).backgroundColor = [UIColor redColor]
continue
解決策 2:再帰的な詳細を出力する
po [0x7f8b9c4... recursiveDescription]
解決策 3:ビューのクラスを確認する
po [0x7f8b9c4... class]
問題:Debug View Hierarchy に制約が表示されない
確認事項:
- デバッグバーの「Show Constraints」ボタン(制約アイコンのようなボタン)をクリックする
- 特定のビューを選択して、右側のパネルでその制約を確認する
- 制約が満たされている(紫色)か、競合している(オレンジ色/赤色)かを確認する
問題:実行時に制約が変化する
確認事項:
- セルやシステムビューに追加される UIKit のシステム制約(UIView-Encapsulated-Layout)
- Dynamic Type による変更(フォントサイズの変更=サイズの無効化)
- 新しい制約を発生させる画面の向きの変更
- ビューコントローラーのライフサイクル(viewDidLoad と viewWillLayoutSubviews)
よくある間違い
❌ コンソールの警告を無視する
誤り:制約の警告を見ても、そのまま作業を続ける。
正解:制約に関する警告はすべて直ちに修正してください。放置すると問題が重なり、後で予測できないレイアウトの原因になります。
❌ 識別子を設定しない
誤り:メモリアドレスで制約をデバッグする。
正解:制約には必ず識別子を設定してください。今の30秒が、後で30分の節約になります。
❌ 制約を過剰に設定する
誤り:leading + trailing + width を設定する。
正解:3つのうち2つを使用します(leading + trailing、または leading + width、または trailing + width)。
❌ Auto Layout とフレームを混在させる
誤り:
imageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100) // Manual frame
imageView.widthAnchor.constraint(equalToConstant: 100).isActive = true // Auto Layout
正解:どちらか一方の方法を選んでください。Auto Layout を使用する場合は、translatesAutoresizingMaskIntoConstraints = false を設定し、位置とサイズを制約で決定します。
実際の効果
導入前(体系的なアプローチなし):
- 制約の競合ごとに30~60分
- 試行錯誤による制約の変更
- 分かりにくいエラーメッセージによるフラストレーション
- 新たな制約を修正するために、正常に動作している制約まで壊してしまう
導入後(体系的なデバッグ):
- 制約の競合ごとに5~10分
- Debug View Hierarchy を使った的確な修正
- 制約に名前を付けることで即座に特定
- シンボリックブレークポイントで問題を即座に捕捉
関連スキル
- Xcode の環境に関する問題については、
axiom-xcode-debuggingスキルを参照してください - SwiftUI のレイアウトに関する問題については、
axiom-swiftui-performanceスキルを参照してください - UI のテストについては、
axiom-ui-testingスキルを参照してください
リソース
ドキュメント:/library/archive/documentation/userexperience/conceptual/autolayoutpg/debuggingtricksandtips
主なポイント
- すべてに名前を付ける — 制約とビューに識別子を設定すると、デバッグにかかる時間を大幅に節約できます
- シンボリックブレークポイントを使用する — 制約の競合を復旧後ではなく、発生元で捕捉します
- Debug View Hierarchy — 推測するのではなく、制約を視覚的に確認します
- メモリアドレス → ビュー — 背景色の手法を使えば、正体不明のビューをすぐに特定できます
- 軸ごとに2つの制約 — 制約の過剰設定を避けます(leading + trailing + width は競合します)
- 優先度が重要 — 必須項目には .required(1000)を、希望する項目には .defaultHigh(750)を使用します
- 体系的な方法が効果的 — ワークフローに従うことで、競合ごとに30~50分を節約できます
最終更新:2024 最小要件:Xcode 12+、iOS 11+(シンボリックブレークポイントはすべてのバージョンで動作します)