mcpField
A
axiom-auto-layout-debugging

axiom-auto-layout-debugging

github.com/charleswiltgenデザイン・UI1.2k176 回インストール今日更新
リポジトリ

axiom-auto-layout-debugging について

Auto Layoutを扱うiOS開発者向けのデバッグスキルです。Xcodeのツールで競合するビューや制約を特定し、よくあるレイアウト問題を調べる手順をまとめています。

主な機能

  1. 01シンボリックブレークポイントで競合を特定
  2. 02メモリアドレスからビューを識別
  3. 03ビュー階層と制約を視覚的に確認
  4. 04曖昧なレイアウトと優先度を分析
  5. 05デバッグ用に制約とビューへラベル付け

活用シーン

  1. 01コンソールの制約警告を調査
  2. 02実行時に位置やサイズがずれるビューを特定
  3. 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)>

主な構成要素:

  1. メモリアドレス — 0x7f8b9c4... はビューと制約を識別します
  2. Visual Format — 制約を人が読める形式で説明します
  3. (active) ステータス — 制約が現在適用されていることを示します
  4. 復旧時の動作 — どの制約システムが制約を破棄するかを示します(通常は優先度が最も低いものです)
システム生成の制約

UIView-Encapsulated-Layout-Width/Height:

  • UIKit がセルやシステムビュー用に作成します
  • 多くの場合、競合の原因になります
  • 通常は正しく、問題は独自の制約にあります

Autoresizing Mask の制約:

  • 形式:h=--& または v=&--
  • - = 固定サイズ
  • & = 可変サイズ
  • 例:h=--& = 左マージンと幅が固定され、右マージンが可変

デバッグのワークフロー

ステップ 1:シンボリックブレークポイントを設定する(初回のみ)

目的:制約の競合が発生したとき、システムが制約を破棄する前に実行を中断します。

設定方法:

  1. Breakpoint Navigator を開きます(⌘+7 または ⌘+8)
  2. + →「Symbolic Breakpoint」をクリックします
  3. Symbol:UIViewAlertForUnsatisfiableConstraints
  4. (任意)Action を追加 →「Sound」→ サウンドを選択します
  5. (任意)「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 を使う

使用するタイミング:ビューの位置が正しくない場合や、コード内で制約を確認できない場合です。

ワークフロー:

  1. 問題を再現する — 制約に問題がある画面に移動します
  2. 実行を一時停止する — デバッグバーの「Debug View Hierarchy」ボタンをクリックします(または Debug → View Debugging → Capture View Hierarchy を選択します)
  3. 3D ビューを調べる — ビュー階層を回転させてレイヤーを確認します
  4. 「Show Constraints」を有効にする — すべての制約を線で表示します
  5. ビューを選択する — 右側のパネルに、選択したビューに影響するすべての制約が表示されます

主な機能:

  • Show Clipped Content — 画面外に配置されたビューを確認できます
  • Show Constraints — 制約の関係を視覚化します
  • Filter Bar — クラス名やメモリアドレスで特定のビューを検索します

問題の特定:

  • 紫色の制約 = 制約を満たしています
  • オレンジ色/赤色の制約 = 競合しています
  • 制約を選択 → 接続されている両方のビューを確認できます

ステップ 4:制約に名前を付ける(予防策)

理由:エラーメッセージが、解読しにくいメモリアドレスではなく、読みやすくなります。

Interface Builder の場合(Storyboard/XIB)
  1. ドキュメントアウトラインで制約を選択します
  2. アトリビュートインスペクタを開きます
  3. 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 の場合
  1. ドキュメントアウトラインでビューを選択します
  2. アイデンティティインスペクタを開きます
  3. 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 は正しく動作していますが、視覚的なデバッグが分かりにくい場合があります。制約のデバッグには元のフレームを使用してください。


トラブルシューティング

問題:ブレークポイントに到達しない

確認事項:

  1. シンボリックブレークポイントのシンボルが正確に UIViewAlertForUnsatisfiableConstraints であること
  2. ブレークポイントが有効になっていること(チェックマークが表示されていること)
  3. 実際に制約の競合が発生していること(コンソールでエラーメッセージを確認してください)

問題:メモリアドレスからビューを特定できない

解決策 1:背景色の手法を使用する

expr ((UIView *)0x7f8b9c4...).backgroundColor = [UIColor redColor]
continue

解決策 2:再帰的な詳細を出力する

po [0x7f8b9c4... recursiveDescription]

解決策 3:ビューのクラスを確認する

po [0x7f8b9c4... class]

問題:Debug View Hierarchy に制約が表示されない

確認事項:

  1. デバッグバーの「Show Constraints」ボタン(制約アイコンのようなボタン)をクリックする
  2. 特定のビューを選択して、右側のパネルでその制約を確認する
  3. 制約が満たされている(紫色)か、競合している(オレンジ色/赤色)かを確認する

問題:実行時に制約が変化する

確認事項:

  1. セルやシステムビューに追加される UIKit のシステム制約(UIView-Encapsulated-Layout)
  2. Dynamic Type による変更(フォントサイズの変更=サイズの無効化)
  3. 新しい制約を発生させる画面の向きの変更
  4. ビューコントローラーのライフサイクル(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


主なポイント

  1. すべてに名前を付ける — 制約とビューに識別子を設定すると、デバッグにかかる時間を大幅に節約できます
  2. シンボリックブレークポイントを使用する — 制約の競合を復旧後ではなく、発生元で捕捉します
  3. Debug View Hierarchy — 推測するのではなく、制約を視覚的に確認します
  4. メモリアドレス → ビュー — 背景色の手法を使えば、正体不明のビューをすぐに特定できます
  5. 軸ごとに2つの制約 — 制約の過剰設定を避けます(leading + trailing + width は競合します)
  6. 優先度が重要 — 必須項目には .required(1000)を、希望する項目には .defaultHigh(750)を使用します
  7. 体系的な方法が効果的 — ワークフローに従うことで、競合ごとに30~50分を節約できます

最終更新:2024 最小要件:Xcode 12+、iOS 11+(シンボリックブレークポイントはすべてのバージョンで動作します)

コメント