Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Nested NSStackViews lead to ambiguous layout
import Cocoa
final class TestView : NSView {
static func makeAndAttachToView(_ rootView: NSView) {
let view = TestView()
view.translatesAutoresizingMaskIntoConstraints = false
rootView.addSubview(view)
NSLayoutConstraint.activate([
view.leadingAnchor .constraint(equalTo: rootView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: rootView.trailingAnchor),
view.topAnchor .constraint(equalTo: rootView.topAnchor),
view.bottomAnchor .constraint(equalTo: rootView.bottomAnchor)
])
}
override init(frame: NSRect) {
super.init(frame: frame)
NSLayoutConstraint.activate([
widthAnchor .constraint(equalToConstant: 320),
heightAnchor.constraint(equalToConstant: 200)
])
func makeStackView(_ orientation : NSUserInterfaceLayoutOrientation,
_ alignment : NSLayoutConstraint.Attribute,
_ view : NSView)
-> NSStackView
{
// The subclasses are just for debugging in the Xcode View Debugger
let v = NSStackView(views: [view])
v.translatesAutoresizingMaskIntoConstraints = false
v.orientation = orientation
v.alignment = alignment
v.spacing = 2.0
return v
}
let searchField = NSSearchField(frame: NSMakeRect(0, 0, 128, 128))
#if false // not ambiguous
let view = makeStackView(.horizontal, .centerY, searchField)
#else // ambiguous
let view = makeStackView(.horizontal, .centerY,
makeStackView(.vertical, .width, // .leading
searchField)
)
#endif
addSubview(view)
NSLayoutConstraint.activate([
view.leadingAnchor .constraint(equalTo: self.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
view.topAnchor .constraint(equalTo: self.topAnchor),
view.bottomAnchor .constraint(equalTo: self.bottomAnchor)
])
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@helje5
Copy link
Author

helje5 commented Nov 27, 2018

The setup seems simple: We have a search-field, which is nested in a v-stack which itself is nested in an h-stack. As soon as I nest one stack in another, the layout becomes ambiguous. And I'm not quite sure why.

Looks right (a more complex variant jumps a few pixels now and then, hence it seems to be a proper ambiguity):
Looks right

But Xcode says the height/y-pos are ambiguous:
Xcode View Debugger

  1. The vertical position is ambiguous because the height is ambiguous (need the height to calculate the .centerY).
  2. And the height is ambiguous, because the height of the inner stack view is >= the height of its contents?

What is the proper fix? Explicitly constrain the hstack to its largest child? That seems to defeat the idea of a stack view?


P.S.: To test the code, just create a new AppKit Xcode project, wire up the window's root view to an outlet, then attach the TestView using TestView.makeAndAttachToView(rootView), like so:

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
  @IBOutlet weak var window   : NSWindow!
  @IBOutlet weak var rootView : NSView!
  
  func applicationDidFinishLaunching(_ aNotification: Notification) {
    TestView.makeAndAttachToView(rootView)
  }
}

@helje5
Copy link
Author

helje5 commented Nov 27, 2018

This removes the ambiguity:

      let innerStack =  makeStackView(.vertical, .width, // .leading
                                      searchField)
      innerStack.setHuggingPriority(.defaultHigh, for: .vertical) // <=
    
      let view = makeStackView(.horizontal, .centerY, innerStack)

Note: it really is the NSStackView specific setHuggingPriority(_:for:), not setContentHuggingPriority(_:for:), which still results in the ambiguity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment