topLayoutGuideが曖昧(AMBIGUOUS)でハマるケース


概要

謎のレイアウト崩れ。autolayoutでハマって半日以上浪費したので備忘録。

多分、autolayoutの最適化周りのバグ。view構造をバラして展開してくれるのはいいんだけど、それ故にあっちのアレが明後日の場所でエラーりましたよ、と。

結論としては、

  • navigationViewControllerのせいでまだ曖昧なとこがあるうちに
  • viewControllerのviewに貼り付けたCustomViewの中で使ってたautolayoutを解決しようとして
  • view構造を展開してから解決しようとして
  • 曖昧なままのUILayoutGuide(topLayoutGuide)との関係性が生まれ、計算しようとして、エラー。
  • エラーったから、適当に解決するね → レイアウト崩れ

ということらしく。

多分タイミングというか依存性解決に失敗してる感あるなあと、試しに
適当なタイミング(viewWillAppear:)でviewControllerに

self.view.layoutIfNeeded()

させておいたら直るという。

ほんと やめて、っていう。

状況

いつの間にか、レイアウト崩れが発生している!?
なんかよくわからんけど、autolayoutがエラーって、勝手にConstraintぶっ壊してくれる、、、
なにこれ。

2018-08-07 16:42:05.366607+0900 HogeFuga Develop[65798:3081762] [LayoutConstraints] Unable to simultaneously satisfy constraints.
    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. 
(
    "<_UILayoutSupportConstraint:0x600000286180 _UILayoutGuide:0x7f8602071450.height == 0   (active)>",
    "<_UILayoutSupportConstraint:0x6000002858c0 V:|-(0)-[_UILayoutGuide:0x7f8602071450]   (active, names: '|':UIView:0x7f8602069610 )>",
    "<NSLayoutConstraint:0x600000287df0 UIStackView:0x7f8602069800.height == 38   (active)>",
    "<NSLayoutConstraint:0x6000002871c0 V:|-(0)-[HogeFuga.THCalendarWeekdaysView:0x7f8602062bb0]   (active, names: '|':HogeFuga.THCalendarView:0x7f8602062670 )>",
    "<NSLayoutConstraint:0x600000287170 HogeFuga.THCalendarWeekdaysView:0x7f8602062bb0.height == 22   (active)>",
    "<NSLayoutConstraint:0x600000287120 V:[HogeFuga.THCalendarWeekdaysView:0x7f8602062bb0]-(0)-[UICollectionView:0x7f86038c4800]   (active)>",
    "<NSLayoutConstraint:0x6000002882a0 V:[UIStackView:0x7f8602069800]-(0)-[HogeFuga.THCalendarView:0x7f8602062670]   (active)>",
    "<NSLayoutConstraint:0x600000288340 V:[_UILayoutGuide:0x7f8602071450]-(0)-[UIStackView:0x7f8602069800]   (active)>",
    "<NSAutoresizingMaskLayoutConstraint:0x600000090c70 h=-&- v=-&- 'UIView-Encapsulated-Layout-Top' UIView:0x7f8602069610.minY == 0   (active, names: '|':UIViewControllerWrapperView:0x7f8602071d90 )>"
)
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600000287120 V:[HogeFuga.THCalendarWeekdaysView:0x7f8602062bb0]-(0)-[UICollectionView:0x7f86038c4800]   (active)>

読むと、上から下に流れる感じの変哲のないレイアウトで、エラーる要因がない。
wtfautolayout.com とかで眺めても、どう見ても正しい。
おかしい。

まさか一番上の_UILayoutGuide:0x7f8602071450が悪いとは(ry

調べる

まずは

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.

と言われるままに
UIViewAlertForUnsatisfiableConstraintsとかいうシンボルにブレークポイントを設定。

すると、変なとこで引っかかる。

UINavigationBar:0x7fd502e0fc90
|   _UIBarBackground:0x7fd502e0c1e0
|   |   UIImageView:0x7fd502e11f60
|   |   UIVisualEffectView:0x7fd502e12190
|   |   |   _UIVisualEffectBackdropView:0x7fd502e5fa90
|   |   |   _UIVisualEffectSubview:0x7fd502e5eec0
|   _UINavigationBarLargeTitleView:0x7fd502e14020'20178月'
|   |   UILabel:0x7fd502e15390
|   _UINavigationBarContentView:0x7fd502e123f0'20178月'
|   |   *<UILayoutGuide: 0x6080001a6040 - "BackButtonGuide(0x7fd502e129e0)", layoutFrame = {{0, 0}, {24, 44}}, owningView = <_UINavigationBarContentView: 0x7fd502e123f0; frame = (0 0; 375 44); layer = <CALayer: 0x60800002bbc0>>>
|   |   *<UILayoutGuide: 0x6080001a6120 - "LeadingBarGuide(0x7fd502e129e0)", layoutFrame = {{24, 0}, {0, 44}}, owningView = <_UINavigationBarContentView: 0x7fd502e123f0; frame = (0 0; 375 44); layer = <CALayer: 0x60800002bbc0>>>- AMBIGUOUS LAYOUT for UILayoutGuide:0x6080001a6120'LeadingBarGuide(0x7fd502e129e0)'.minX{id: 610}, UILayoutGuide:0x6080001a6120'LeadingBarGuide(0x7fd502e129e0)'.Width{id: 613}
|   |   *<UILayoutGuide: 0x6080001a6200 - "TitleView(0x7fd502e129e0)", layoutFrame = {{24, 0}, {343, 44}}, owningView = <_UINavigationBarContentView: 0x7fd502e123f0; frame = (0 0; 375 44); layer = <CALayer: 0x60800002bbc0>>>- AMBIGUOUS LAYOUT for UILayoutGuide:0x6080001a6200'TitleView(0x7fd502e129e0)'.minX{id: 612}, UILayoutGuide:0x6080001a6200'TitleView(0x7fd502e129e0)'.Width{id: 616}
|   |   *<UILayoutGuide: 0x6080001a62e0 - "TrailingBarGuide(0x7fd502e129e0)", layoutFrame = {{367, 0}, {0, 44}}, owningView = <_UINavigationBarContentView: 0x7fd502e123f0; frame = (0 0; 375 44); layer = <CALayer: 0x60800002bbc0>>>- AMBIGUOUS LAYOUT for UILayoutGuide:0x6080001a62e0'TrailingBarGuide(0x7fd502e129e0)'.minX{id: 615}, UILayoutGuide:0x6080001a62e0'TrailingBarGuide(0x7fd502e129e0)'.Width{id: 619}
|   |   *<UILayoutGuide: 0x6080001a63c0 - "UIViewLayoutMarginsGuide", layoutFrame = {{16, 0}, {343, 44}}, owningView = <_UINavigationBarContentView: 0x7fd502e123f0; frame = (0 0; 375 44); layer = <CALayer: 0x60800002bbc0>>>
|   |   *UILabel:0x7fd502f42300'20178月'
|   +_UINavigationBarModernPromptView:0x7fd502e19fb0
|   |   *UILabel:0x7fd502e1a4e0

AMBIGUOUS LAYOUT for UILayoutGuideと出てるのが、autolayout的に曖昧なトコロ。

これ、どういうこと?
関係あるの?とちゃんと見比べていれば、あるいはすぐ問題解決できたかもしれません。
どゆこと?と他に理由を探したのが間違い。

わからないので、一番怪しそうなNSAutoresizingMaskLayoutConstraintを殺すべく、translatesAutoresizingMaskIntoConstraintsをfalseにしてみてくが
ViewControllerのviewにresizingMask付けなかったらダメじゃん、と、つまり違うらしい。

続いてAutolayoutGuide::デバッグに役立つヒントとかを眺めつつ
デバッガコンソールでconstraintsAffectingLayoutForAxisとかを叩くも、返ってくるのは期待してる通りの正常っぽい結果。

もはや誰が敵かわからなくなり、collectionViewだからいけないのではないか、とか
いやあるいはstoryboardのオプションが、とか色々試し出す。
混乱と疑心暗鬼。

解決

以前は動いていた、今は動いてない。
ならば、前の状況に近付けて、切り分けを、、、は面倒なので

代わりに、ばっさばっさとコメントアウトを行い、ちゃんと動く状態を作り出す。
Storyboard上で配置されてる要素を ばっさばっさと捨てながら、動くところまで持っていく。

  • 縦方向のレイアウトの問題

なのは自明だったので、それに基づいて、色々試して傾向をみる。

結果的に、上側のConstraintを切るとちゃんと動いたので、ええーっと、となり
それまで試したCompressionResistancePriorityなり
intrinsicContentSizeを設定してみたりと努力は無駄でした。ちゃんちゃん。

topLayoutGuideとかOS(ランタイム)側がよしなにしてくれるトコの認識だから
まさかそんなのが悪いとか思い付かなかったよ・・・

再現性

もう精魂果てたので、誰か暇なら再現性確認してバグレポート投げて、、、