Developing on Staxmanade

Debugging iOS AutoLayout Issues

(Comments)

This tip may not be news to you, but it was to me so I'll put this up here to help you, but more to help myself when I get stuck down the road and forget how to do this.

Background

I just stumbled upon a way to help when debugging and diagnosing issues around ambiguous auto layout constraints (you know the kind where iOS just picks the one it feels like to remove?).

Today I was researching why I was receiving the following output in my debugger console:

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. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
    "<NSLayoutConstraint:0x7fc82d3e18a0 H:[UIView:0x7fc82aba1210(768)]>",
    "<NSLayoutConstraint:0x7fc82d6369e0 H:[UIView:0x7fc82aba1210]-(0)-|   (Names: '|':UIView:0x7fc82d6b9f80 )>",
    "<NSLayoutConstraint:0x7fc82d636a30 H:|-(0)-[UIView:0x7fc82aba1210]   (Names: '|':UIView:0x7fc82d6b9f80 )>",
    "<NSLayoutConstraint:0x7fc82d3e7fd0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x7fc82d6b9f80(50)]>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7fc82d3e18a0 H:[UIView:0x7fc82aba1210(768)]>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

Now, I can tell from the above that theres an issue with the height constraint(s), but looking at this, I have no idea what view is being referenced which makes it quite difficult to jump to XCode and inspect the view's constraints.

But I worked through it, and learned the following along the way. Hope this helps you.

How to set auto layout breakpoint in Xcode debugger

Using this text Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. I found this great post on stack overflow on How to trap on UIViewAlertForUnsatisfiableConstraints but have copied below for reference.

You'll want to add a Symbolic Breakpoint. Apple provides an excellent guide on how to do this.

  1. Open the Breakpoint Navigator cmd+7
  2. Click the Add button in the lower left
  3. Select Add Symbolic Breakpoint...
  4. Where it says Symbol just type in UIViewAlertForUnsatisfiableConstraints

You can also treat it like any other breakpoint, turning it on and off, adding actions, or log messages.

But that's only the first step. Once you've run the app and hit the breakpoint, you're left staring at assembly code and memory addresses, for example:

UIKit`UIViewAlertForUnsatisfiableConstraints:
->  0x1131cc4a2 <+0>:   pushq  %rbp
    0x1131cc4a3 <+1>:   movq   %rsp, %rbp
    0x1131cc4a6 <+4>:   pushq  %r15
    0x1131cc4a8 <+6>:   pushq  %r14
    0x1131cc4aa <+8>:   pushq  %rbx
    0x1131cc4ab <+9>:   pushq  %rax
    0x1131cc4ac <+10>:  movq   %rsi, %r14
    0x1131cc4af <+13>:  movq   %rdi, %r15
    0x1131cc4b2 <+16>:  cmpq   $-0x1, 0x778796(%rip)     ; _UIConstraintBasedLayoutPlaySoundOnUnsatisfiable.result + 7
    0x1131cc4ba <+24>:  jne    0x1131cc529               ; <+135>
    0x1131cc4bc <+26>:  cmpb   $0x0, 0x778785(%rip)      ; _UIConstraintBasedLayoutPlaySoundWhenEngaged.__once + 7
    0x1131cc4c3 <+33>:  je     0x1131cc4ed               ; <+75>
    0x1131cc4c5 <+35>:  movq   0x73cd8c(%rip), %rdi      ; (void *)0x0000000113925248: UIDevice
    0x1131cc4cc <+42>:  movq   0x7108e5(%rip), %rsi      ; "currentDevice"
    0x1131cc4d3 <+49>:  movq   0x4d7aee(%rip), %rbx      ; (void *)0x0000000114598000: objc_msgSend
    0x1131cc4da <+56>:  callq  *%rbx
    0x1131cc4dc <+58>:  movq   0x71e375(%rip), %rsi      ; "_playSystemSound:"
    0x1131cc4e3 <+65>:  movl   $0x3ee, %edx
    0x1131cc4e8 <+70>:  movq   %rax, %rdi
    0x1131cc4eb <+73>:  callq  *%rbx
    0x1131cc4ed <+75>:  cmpq   $-0x1, 0x77877b(%rip)     ; _UIConstraintBasedLayoutLogUnsatisfiable.result + 7
    0x1131cc4f5 <+83>:  jne    0x1131cc541               ; <+159>
    0x1131cc4f7 <+85>:  cmpb   $0x0, 0x77876a(%rip)      ; _UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints.__once + 7
    0x1131cc4fe <+92>:  je     0x1131cc51e               ; <+124>
    0x1131cc500 <+94>:  leaq   0x53ba89(%rip), %rdi      ; @"Unable to simultaneously satisfy constraints.\n\tProbably 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) \n%@\n\nWill attempt to recover by breaking constraint \n%@\n\nMake a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.\nThe methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful."
    0x1131cc507 <+101>: xorl   %eax, %eax
    0x1131cc509 <+103>: movq   %r14, %rsi
    0x1131cc50c <+106>: movq   %r15, %rdx
    0x1131cc50f <+109>: addq   $0x8, %rsp
    0x1131cc513 <+113>: popq   %rbx
    0x1131cc514 <+114>: popq   %r14
    0x1131cc516 <+116>: popq   %r15
    0x1131cc518 <+118>: popq   %rbp
    0x1131cc519 <+119>: jmp    0x11331930c               ; symbol stub for: NSLog
    0x1131cc51e <+124>: addq   $0x8, %rsp
    0x1131cc522 <+128>: popq   %rbx
    0x1131cc523 <+129>: popq   %r14
    0x1131cc525 <+131>: popq   %r15
    0x1131cc527 <+133>: popq   %rbp
    0x1131cc528 <+134>: retq
    0x1131cc529 <+135>: leaq   0x778720(%rip), %rdi      ; _UIConstraintBasedLayoutPlaySoundOnUnsatisfiable.__once
    0x1131cc530 <+142>: leaq   0x4fdb59(%rip), %rsi      ; __block_literal_global68
    0x1131cc537 <+149>: callq  0x11331a2a8               ; symbol stub for: dispatch_once
    0x1131cc53c <+154>: jmp    0x1131cc4bc               ; <+26>
    0x1131cc541 <+159>: leaq   0x778728(%rip), %rdi      ; _UIConstraintBasedLayoutLogUnsatisfiable.__once
    0x1131cc548 <+166>: leaq   0x4fdbc1(%rip), %rsi      ; __block_literal_global76
    0x1131cc54f <+173>: callq  0x11331a2a8               ; symbol stub for: dispatch_once
    0x1131cc554 <+178>: jmp    0x1131cc4f7               ; <+85>

What next?

We can use the Xcode debug console output to gather more information than we see it initially provides.

In the above example I'm going to take the following output:

Will attempt to recover by breaking constraint <NSLayoutConstraint:0x7fc82d3e18a0 H:[UIView:0x7fc82aba1210(768)]>

Pull out the UIView's memory address 0x7fd8fe59a440 and use the XCode console to dig deeper and try to get a better understanding.

Printing views from memory addresses

The memory address 0x7fd8fe59a440 is a pointer to a UIView. Since we know that a UIView has other selectors we can query, we can begin to use these to dig deeper into the object and get a better understanding of which part of our view have auto layout constraints that are not playing nicely.

First we try to look at just the UIView and see if that helps us get a grasp as to which view is causing our troubles.

po 0x7fc82aba1210

If this doesn't provide enough info as the below sample output shows (not very helpful).

(lldb) po 0x7fc82aba1210
<UIView: 0x7fc82aba1210; frame = (0 0; 768 359); autoresize = RM+BM; layer = <CALayer: 0x7fc82d338960>>

Maybe printing it's recursiveDescription will help out and in my case gives me a better idea of which component I'm actually trying to look at.

(lldb) po [0x7fc82aba1210 recursiveDescription]
<UIView: 0x7fc82aba1210; frame = (0 0; 768 359); autoresize = RM+BM; layer = <CALayer: 0x7fc82d338960>>
   | <MYAPPButton: 0x7fc82d61c800; baseClass = UIButton; frame = (0 0; 768 359); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7fc82ab96570>>
   |    | <UIImageView: 0x7fc82d3f7b10; frame = (0 0; 0 0); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fc82d6e7020>>
   | <UIImageView: 0x7fc82d54a1c0; frame = (314 110; 140 140); autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x7fc82d52def0>>

Or if that's not enough context, then we can look at the view's superview and potentially even walk up the view's tree, printing out a different levels trying to understand which component is having trouble with auto layout.

po [[0x7fc82aba1210 superview] recursiveDescription]

Once you've narrowed down which view inside which component is causing you trouble, now it's up to digging and finding the problematic constraint...

Fix it

This part I can't add much color to help you, except to start by looking in your storyboard, xib or code wherever the constraints are being added to the particular view we diagnosed above.

Hopefully this walk-through helps at least a little in diagnosing your auto layout constraint issues.

Happy debugging!