Dynamically adding cells to a NSMatrix laid out with Auto Layout has weird effects; why?

289 views Asked by At

I want to create a group of radio buttons using the NSMatrix method that Interface Builder uses, but in code. The matrix is laid out using Auto Layout. I have it mostly working, except for when I add new options at runtime.

In the following example, clicking Append Item a few times will work fine, then the matrix starts going out of the window near the top (at least I think it's clipped at the top). If you maximize this window after adding a bunch of items, the window will stay the same height and all the items will be clipped to about a pixel high each, which is a very undesirable thing :)

In my real program (not this test below), it works mostly fine, but if I add an option dynamically, after certain numbers of items (initially 5), the options will clip very slightly, appearing slightly squeezed or squished. Adding another option reverts this until the next magic number is hit.

What's going on? I'm testing this on OS X Yosemite. Thanks.

// 17 august 2015
import Cocoa

var keepAliveMainwin: NSWindow? = nil
var matrix: NSMatrix? = nil

class ButtonHandler : NSObject {
    @IBAction func onClicked(sender: AnyObject) {
        var lastRow = matrix!.numberOfRows
        matrix!.renewRows(lastRow + 1, columns: 1)
        var cell = matrix!.cellAtRow(lastRow, column: 0) as! NSButtonCell
        cell.title = "New Item"
        matrix!.sizeToCells()
    }
}

var buttonHandler: ButtonHandler = ButtonHandler()

func appLaunched() {
    var mainwin = NSWindow(
        contentRect: NSMakeRect(0, 0, 320, 240),
        styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask),
        backing: NSBackingStoreType.Buffered,
        defer: true)
    var contentView = mainwin.contentView as! NSView

    var prototype = NSButtonCell()
    prototype.setButtonType(NSButtonType.RadioButton)
    prototype.font = NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSControlSize.RegularControlSize))

    matrix = NSMatrix(frame: NSZeroRect,
        mode: NSMatrixMode.RadioModeMatrix,
        prototype: prototype,
        numberOfRows: 0,
        numberOfColumns: 0)
    matrix!.allowsEmptySelection = false
    matrix!.selectionByRect = true
    matrix!.intercellSpacing = NSMakeSize(4, 2)
    matrix!.autorecalculatesCellSize = true
    matrix!.drawsBackground = false
    matrix!.drawsCellBackground = false
    matrix!.autosizesCells = true
    matrix!.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(matrix!)

    var button = NSButton(frame: NSZeroRect)
    button.title = "Append Item"
    button.setButtonType(NSButtonType.MomentaryPushInButton)
    button.bordered = true
    button.bezelStyle = NSBezelStyle.RoundedBezelStyle
    button.font = NSFont.systemFontOfSize(NSFont.systemFontSizeForControlSize(NSControlSize.RegularControlSize))
    button.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(button)

    button.target = buttonHandler
    button.action = "onClicked:"

    var views: [String: NSView]
    views = [
        "button":   button,
        "matrix":   matrix!,
    ]
    addConstraints(contentView, "V:|-[matrix]-[button]-|", views)
    addConstraints(contentView, "H:|-[matrix]-|", views)
    addConstraints(contentView, "H:|-[button]-|", views)

    mainwin.cascadeTopLeftFromPoint(NSMakePoint(20, 20))
    mainwin.makeKeyAndOrderFront(mainwin)
    keepAliveMainwin = mainwin
}

func addConstraints(view: NSView, constraint: String, views: [String: NSView]) {
    var constraints = NSLayoutConstraint.constraintsWithVisualFormat(
        constraint,
        options: NSLayoutFormatOptions(0),
        metrics: nil,
        views: views)
    view.addConstraints(constraints)
}

class appDelegate : NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(note: NSNotification) {
        appLaunched()
    }

    func applicationShouldTerminateAfterLastWindowClosed(app: NSApplication) -> Bool {
        return true
    }
}

func main() {
    var app = NSApplication.sharedApplication()
    app.setActivationPolicy(NSApplicationActivationPolicy.Regular)
    // NSApplication.delegate is weak; if we don't use the temporary variable, the delegate will die before it's used
    var delegate = appDelegate()
    app.delegate = delegate
    app.run()
}

main()
1

There are 1 answers

0
Ken Thomases On BEST ANSWER

Apparently, you need to omit the call to sizeToCells() after calling renewRows(_:columns:). My guess is that it sets the frame size, which is mostly useless when using auto layout, but also clears a "dirty" flag somewhere that tells the matrix that it needs to invalidate its intrinsic size. In other words, the matrix thinks it already did the re-layout stuff it needed to do.