Skip to content

Instantly share code, notes, and snippets.

@helje5
Created November 27, 2018 09:45
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save helje5/0456aafe2a27b4ed37ce08bb7a53f133 to your computer and use it in GitHub Desktop.
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