NSSplitViewController Auto Layout Bug and Workaround
31st March, 2015 —
You can’t constrain this!
NSSplitViewController has an edge-case Auto Layout bug that manifests when a split view item is set as collapsed in Interface Builder (IB) and then shown at some point after viewDidLoad
(e.g., in response to a user action).
It actually took me two days to track down this little rascal so I took the time to create a simple isolated example that replicates the issue.
Download the source here. (Test.zip, 68KB)
The exception thrown by the sample is below:
Error console output:2015-03-31 13:46:32.669 Test[7890:1234827] Unable to simultaneously satisfy constraints:
(
"<NSLayoutConstraint:0x6080000850a0 H:[_NSSplitViewItemViewWrapper:0x6000001411e0(50)]>",
"<NSLayoutConstraint:0x6000000879e0 H:|-(0)-[NSView:0x6000001221c0] (Names: '|':_NSSplitViewItemViewWrapper:0x6000001411e0 )>",
"<NSLayoutConstraint:0x600000087a30 H:[NSView:0x6000001221c0]-(0)-| (Names: '|':_NSSplitViewItemViewWrapper:0x6000001411e0 )>",
"<NSLayoutConstraint:0x600000087620 H:[NSBox:0x10052d0a0'Box'(560)]>",
"<NSLayoutConstraint:0x600000087760 H:|-(20)-[NSBox:0x10052d0a0'Box'] (Names: '|':NSView:0x6000001221c0 )>",
"<NSLayoutConstraint:0x6000000877b0 H:[NSBox:0x10052d0a0'Box']-(20)-| (Names: '|':NSView:0x6000001221c0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600000087620 H:[NSBox:0x10052d0a0'Box'(560)]>
Set the NSUserDefault NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints to YES to have -[NSWindow visualizeConstraints:] automatically called when this happens. And/or, break on objc_exception_throw to catch this in the debugger.
As far as I can tell, the problem appears to be this: When Cocoa sees a collapsed split view item, it automatically creates a width constraint with required priority on the also-automatically-created _NSSplitViewItemViewWrapper
. Then, if your split view item’s view also has a required width constraint of some sort, they conflict when you set collapsed = false
later on.
Workaround
The isolated example I’ve provided should hopefully make it easy for Apple to fix this but, in the meanwhile, there is an easy workaround: Either,
- Leave the Collapsed checkbox unchecked in IB and set the split view item’s
collapsed
property to true in theviewDidLoad
method of yourNSSplitViewController
subclass. - Check the Collapsed checkbox in IB and then, in your
NSSplitViewController
subclass’sviewDidLoad
method do:Swift:
Simply setting themySplitViewItem.collapsed = false mySplitViewItem.collapsed = true
collapsed
property tofalse
momentarily and then setting it totrue
is enough, even though it happens on the same stack frame. I can only assume that doing so results in the width constraint of the view being measured and then correctly and applied to the_NSSplitViewItemViewWrapper
’s width constraint.
I hope this helps you work around the problem and makes it easier for the folks at Apple to fix it. (rdar://20432741)