デバッグの秘訣とヒント
以下のトピックスでは、レイアウトに関する情報を収集・整理する方法と、発生する可能性があるいくつかの驚くべき動作について説明します。すべてのレイアウトでこれらのテクニックを使用する必要はないかもしれませんが、最も困難な問題を解決するのに役立ちます。
ログの理解
ビューに関する情報は、満たされないレイアウトがあるか、constraintsAffectingLayoutForAxis: または constraintAffectingLayoutForOrientation: デバッグメソッドを使用して制約を明示的にログに記録したため、コンソールに出力できます。
どちらの方法でも、これらのログで多くの有用な情報を見つけることができます。以下に、満たされないレイアウトエラーの出力例を示します。
1 2015-08-26 14:27:54.790 Auto Layout Cookbook[10040:1906606] Unable to simultaneously satisfy constraints. 2 Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 3 ( 4 "<NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]>", 5 "<NSLayoutConstraint:0x7a895e30 UILabel:0x7a8724b0'Name'.leading == UIView:0x7a887ee0.leadingMargin>", 6 "<NSLayoutConstraint:0x7a886d20 H:[UILabel:0x7a8724b0'Name']-(NSSpace(8))-[ UITextField:0x7a88cff0]>", 7 "<NSLayoutConstraint:0x7a87b2e0 UITextField:0x7a88cff0.trailing == UIView:0x7a887ee0.trailingMargin>", 8 "<NSLayoutConstraint:0x7ac7c430 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7a887ee0(320)]>" 9 ) 10 11 Will attempt to recover by breaking constraint 12 <NSLayoutConstraint:0x7a87b000 H:[UILabel:0x7a8724b0'Name'(>=400)]> 13 14 Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. 15 The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
このエラーメッセージは、5 つの矛盾する制約を示しています。これらの制約のすべてが同時に当てはまるわけではありません。その 1 つを削除するか、オプションの制約に変換する必要があります。
幸い、ビュー階層は比較的単純です。ラベルとテキストフィールドを含むスーパービューがあります。矛盾する制約により、以下の関係が設定されます。
- ラベルの幅は 400 ポイント以上です。
- ラベルの先端は、スーパービューの先端マージンと同じです。
- ラベルとテキストフィールドの間に 8 ポイントのスペースがあります。
- テキストフィールドの後端は、スーパービューの後端マージンと同じです。
- スーパービューの幅は 320 ポイントに設定されています。
システムは、ラベルの幅を解除して回復を試みます。
制約は、Visual Format Language を使用してコンソールに書き込まれます。視覚書式言語 (Visual Format Language) を使用して独自の制約を作成したことが全くない場合でも、自動レイアウトの問題を効果的にデバッグするには、それを読んで理解できなければなりません。詳細については、視覚書式言語 を参照してください。
これらの制約のうち、最後の制約はシステムによって作成されました。変更することはできません。さらに、最初の制約との明らかな矛楯が発生します。スーパービューの幅が 320 ポイントしかない場合、400 ポイント幅のラベルを付けることはできません。幸い、最初の制約を取り除く必要はありません。優先度を 999 に下げても、システムはまだ選択された幅を提供しようとしますが、他の制約も満たしながら、可能な限り近くなります。
ビューの自動サイズ変更マスクに基づく制約 (たとえば、translatesAutoresizingMaskIntoConstraints が YES の場合に作成される制約) には、マスクに関する追加情報があります。制約のアドレスの後、ログ文字列には h= の後に 3 文字が続き、v= の後に 3 文字が続きます。- (ハイフン) 文字は固定値を示し、& (アンパサンド) は柔軟な値を示します。水平マスク (h=) の場合、3 つの文字は左マージン、幅、および右マージンを示します。垂直マスク (v=) の場合、上マージン、高さ、下マージンを示します。
例えば、ログメッセージについて考えてみます。
<NSAutoresizingMaskLayoutConstraint:0x7ff28252e480 h=--& v=--& H:[UIView:0x7ff282617cc0(50)]>"
このメッセージは以下の部分で構成されています。
- NSAutoresizingMaskLayoutConstraint:0x7ff28252e480: 制約のクラスとアドレス。この例では、クラスはビューの自動サイズ変更マスクに基づいていることを示しています。
- h=-& v=—&: ビューの自動サイズ変更マスク。これがデフォルトのマスクです。水平方向には、固定左マージン、固定幅、および柔軟な右マージンがあります。垂直方向には、固定上部マージン、固定の高さ、および柔軟な下部マージンがあります。つまり、スーパービューのサイズが変更されても、ビューの左上隅とサイズは一定のままです。
- H:[UIView:0x7ff282617cc0(50)]: 制約の視覚書式言語の記述。この例では、50 ポイントの一定の幅を持つ単一のビューを定義しています。この説明にはまた、制約の影響を受けるビューのクラスとアドレスも含まれています。
ログへの ID の追加
前の例は比較的理解しやすいものでしたが、制約のリストが長くなると、従うことがすぐに難しくなります。すべてのビューと制約に意味のある ID を提供することで、ログを読みやすくすることができます。
ビューに明白なテキストコンポーネントがある場合、Xcode はそれを ID として使用します。たとえば、Xcode はラベルのテキスト、ボタンのタイトル、またはテキストフィールドのプレースホルダを使用して、これらのビューを識別します。それ以外の場合は、ID インスペクタでビューの Xcode 固有のラベルを設定します。Interface Builder は、インターフェイス全体でこれらの ID を使用します。それらの多くは、コンソールログにも表示されます。
制約については、プログラムで、または属性インスペクタを使用して、それらの identifier プロパティを設定します。その後自動レイアウトは、コンソールに情報を出力するときにこれらの ID を使用します。
たとえば、ID を設定した場合と同じ満たされない制約エラーは以下のとおりです。
1 2015-08-26 14:29:32.870 Auto Layout Cookbook[10208:1918826] Unable to simultaneously satisfy constraints. 2 Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 3 ( 4 "<NSLayoutConstraint:0x7b58bac0 'Label Leading' UILabel:0x7b58b040'Name'.leading == UIView:0x7b590790.leadingMargin>", 5 "<NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]>", 6 "<NSLayoutConstraint:0x7b58baf0 'Space Between Controls' H:[UILabel:0x7b58b040'Name']-(NSSpace(8))-[UITextField:0x7b589490]>", 7 "<NSLayoutConstraint:0x7b51cb10 'Text Field Trailing' UITextField:0x7b589490.trailing == UIView:0x7b590790.trailingMargin>", 8 "<NSLayoutConstraint:0x7b0758c0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7b590790(320)]>" 9 ) 10 11 Will attempt to recover by breaking constraint 12 <NSLayoutConstraint:0x7b56d020 'Label Width' H:[UILabel:0x7b58b040'Name'(>=400)]> 13 14 Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. 15 The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
ご覧のとおり、これらの ID を使用すると、ログ内の制約をすばやく簡単に認識することができます。
ビューと制約の視覚化
Xcode は、ビューとビュー階層の制約を視覚化するのに役立つツールを提供します。
シミュレーターでビューを表示するには:
- シミュレーターでアプリを実行します。
- Xcode に切り替えます。
- [デバッグ(Debug)] > [デバッグの表示(View Debug)] > [四角形を整列して表示(Show Alignment Rectangles)] を選択します。この設定は、ビューのエッジの輪郭を描きます。
整列した長方形は、自動レイアウトで使用されるエッジです。このオプションをオンにすると、予期せずサイズ変更された整列した長方形をすばやく見つけることができます。
さらに詳しい情報が必要な場合は、Xcode デバッグバーの [デバッグビュー階層(Debug View Hierarchy)] ボタン ()をクリックします。次に、Xcode はインタラクティブなビューデバッガーを表示し、ビュー階層を探索および操作するための多数のツールを提供します。自動レイアウトの問題をデバッグする場合、"クリップされたコンテンツを表示する" および "制約を表示する" オプションが特に役立ちます。
"クリップされたコンテンツを表示" オプションを有効にすると、誤ってスクリーンの外に配置されたビューの場所が表示されます。"制約を表示" オプションを有効にすると、現在選択されているビューに影響を与えるすべての制約が表示されます。どちらのオプションとも、異常な動作が始まったときに迅速な健全性チェックを提供します。
詳細については、デバッグ領域のヘルプ を参照してください。
極端な場合の理解
自動レイアウトが予期しない方法で動作する原因となるいくつかの極端なケースを以下に示します。
- 自動レイアウトでは、フレームではなく整列長方形に基づいてビューを配置します。ほとんどの場合、これらの値は同一です。ただし、一部のビューでは、レイアウトの計算からビューの一部 (たとえば、バッジ) を除外するためにカスタムの整列長方形を設定する場合があります。
- iOS では、ビューの transform プロパティを使用して、ビューのサイズ変更、回転、または移動を行うことができます。ただし、これらの変換は、自動レイアウトの計算には影響しません。自動レイアウトは、変換されていないフレームに基づいてビューの配列長方形を計算します。
- ビューはその境界の外にコンテンツを表示できます。ほとんどの場合、ビューは適切に動作し、コンテンツを境界に制限します。ただし、パフォーマンス上の理由から、これはグラフィックエンジンによっては強制されません。つまり、ビュー (特にカスタム描画を使用したビュー) は、フレームとは異なるサイズで描画される可能性があります。
- NSLayoutAttributeBaseline、NSLayoutAttributeFirstBaseline、および NSLayoutAttributeLastBaseline 属性は、すべてのビューが固有のコンテンツの高さに表示されている場合にのみ、テキストを正しく整列します。ビューの 1 つが垂直方向に拡大または縮小されている場合、そのテキストが誤った位置に表示されることがあります。
- 制約の優先度は、ビュー階層全体を通してグローバルプロパティとして機能します。多くの場合、スタックビュー、レイアウトガイド、またはダミービュー内のビューをグループ化することで、レイアウトを簡略化できます。ただし、このアプローチは、含まれているビューの優先度をカプセル化しません。自動レイアウトは引き続き、グループ内の優先度とグループ外の優先度 (または他のグループ内の優先度さえ) を比較します。
- アスペクト比の制約により、水平方向と垂直方向の制約が相互作用できます。通常、水平レイアウトと垂直レイアウトは別々に計算されます。ただし、ビューの幅を基準にしてその高さを制約すると、垂直拘束と水平方向の制約の間に接続が作成されます。これらは相互に影響を及ぼし、矛盾する可能性があります。これらの相互作用により、レイアウトの複雑さが大幅に増大し、レイアウトの無関係な部分の間で予期しない矛盾が発生する可能性があります。
詳細については、UIView クラスリファレンス の "ビューの自動レイアウトでの整列" を参照してください。
これらのバグを特定するには、ビューの clipsToBounds プロパティを YES に設定するか、ビューのフレームのサイズを確認して下さい。