0

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()
andlabs
  • 11,290
  • 1
  • 31
  • 52
  • What do you expect/want to happen if the matrix is too tall to fit in the window (minus the space for the button)? Have you tried setting `autosizesCells` to false? You want the matrix's intrinsic size to reflect the size of its cells; you don't want the matrix to resize the cells based on its current size. Also, are the compression resistance priorities of the matrix (horizontal and vertical) set as you want? In particular, if you want the window to grow when the matrix gets too tall, you want the compression resistance to be greater than 500 (`NSLayoutPriorityWindowSizeStayPut`). – Ken Thomases Aug 23 '15 at 02:34
  • Good questions. I want the NSMatrix to grow to accommodate the new cell, but without compressing other cells. I did try `autosizesCells` of false in the real program and that didn't have a desired effect, though I don't remember what. I'll also try the compression resistance stuff and report everything back, then edit the question to be a bit more clear. Thanks. – andlabs Aug 23 '15 at 03:24
  • Right, but when the matrix grows taller than the window, what do you expect to happen? Should the window grow? What about when the window is as tall as can fit on the screen? Do you want the matrix to scroll? In that case, it will have to be in a scroll view. – Ken Thomases Aug 23 '15 at 03:54
  • When the matrix grows taller than the window, I want the window to grow itself, even if this would make the window too tall to fit on screen. With autosizing cells to false in the program above, the matrix grows upward off the top of the window each time I add a button. With it set to false in my real program, after adding an item, the matrix stops growing at all, so only the first new item is shown. For both programs, the compression resistance priorities in both directions is 750 (NSLayoutPriorityDefaultHigh), so I have a feeling that isn't it... – andlabs Aug 23 '15 at 21:14
  • 1
    In your action method, try leaving out the call to `sizeToCells()`. If that doesn't change anything, try adding a call to `invalidateIntrinsicContentSize()`. – Ken Thomases Aug 23 '15 at 21:28
  • Yep, removing `sizeToCells()` did it, not just in the program above but also in my real program. I'm guessing it's not compatible with Auto Layout, even if I recreate my constraints after calling it. Thanks! – andlabs Aug 23 '15 at 21:55

1 Answers1

1

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.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154