Search code examples
swiftcocoanstableviewcocoa-bindingsnspopupbuttoncell

Cocoa NSPopUpButtonCell not displaying selected value


Background

I have an NSTableView with 3 columns, one of them has an NSPopUpButtonCell.

I have been unsuccessful at using data bindings to populate the cell data, as such I am now trying a new approach by setting values programmatically with addItemsWithTitles.

While this approach has worked to load the values, it is failing to display the default selected (I'm okay with index 0 of my array) and it is also failing to display any item I select in the popup.

Bindings Issue

The documentations suggest using bindings and provide an example using Core Data to load relational data in the popup. Although my app is set up differently, I still attempted to use an Array Controller but this would not populate the data in the popUpButtoncell.

My data structure is a simple class object, no core data, and it looks like this.

class Display: NSObject {
    var name: String
    var resolution: String
    var availableResolutions: [String]
}

I would like to use the availableResolutions array to populate the dropdown.

Each row in my table represents an instance of the Display object. I want the NSPopUpButtonCell to display the items in that particular Display's list of availableResolutions. I'm not sure if it's even possible to have a dynamic list for each Display row.

What I've tried

I created an IBOutlet for my NSPopUpButtonCell and I'm referencing it in objectValueForTableColumn and setObjectValue. I add the array of availableResultions inside of objectValueForTableColumn.

@IBOutlet weak var popUpCell: NSPopUpButtonCell!

func numberOfRowsInTableView(tableView: NSTableView) -> Int {
    return self.displays.count
}

func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? {
    let display = displays[row]

    if tableColumn!.identifier == "displayCell" {
        return display.name
    }
    else if tableColumn!.identifier == "resolutionCell" {
        return display.resolution
    }
    else {
        self.popUpCell.addItemsWithTitles(display.availableResolutions!)
        return display.availableResolutions
    }
}

func tableView(tableView: NSTableView, setObjectValue object: AnyObject?, forTableColumn tableColumn: NSTableColumn?, row: Int) {

    if tableColumn!.identifier == "displayCell" {
        displays[row].name = object as! String
    }
    else if tableColumn!.identifier == "resolutionCell" {
        object as! String
    }
    else {
        let index = object as! Int
        if index >= 0 {
            self.popUpCell.selectItemAtIndex(index)
            self.popUpCell.synchronizeTitleAndSelectedItem()
        }
    }
}

What am I missing?


Solution

  • With bindings:

    Bind Selected Value of the column to arrangedObjects, resolution.

    Bind Content Values of the column to the same array controller, arrangedObjects, availableResolutions.

    Without bindings:

    The datasource methods tableView:objectValueForTableColumn:row: and tableView:setObjectValue:forTableColumn:row: deal with the selected item. The delegate method tableView:dataCellForTableColumn:row: deals with the items.

    func numberOfRowsInTableView(tableView: NSTableView) -> Int {
        return self.displays.count
    }
    
    func tableView(tableView: NSTableView, objectValueForTableColumn tableColumn: NSTableColumn?, row: Int) -> AnyObject? {
        let display = displays[row]
        if tableColumn!.identifier == "displayCell" {
            return display.name
        }
        else if tableColumn!.identifier == "resolutionCell" {
            return display.availableResolutions.indexOf(display.resolution)
        }
        return nil
    }
    
    func tableView(tableView: NSTableView, setObjectValue object: AnyObject?, forTableColumn tableColumn: NSTableColumn?, row: Int) {
        let display = displays[row]
        if tableColumn!.identifier == "displayCell" {
            display.name = object as! String
        }
        else if tableColumn!.identifier == "resolutionCell" {
            display.resolution = display.availableResolutions[object as! Int]
        }
    }
    
    func tableView(tableView: NSTableView, dataCellForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSCell? {
        if let cell = tableColumn?.dataCellForRow(row) as? NSCell {
            if tableColumn!.identifier == "resolutionCell" {
                if let popupButtonCell = cell as? NSPopUpButtonCell {
                    popupButtonCell.removeAllItems()
                    popupButtonCell.addItemsWithTitles(displays[row].availableResolutions)
                }
            }
            return cell
        }
        return nil
    }
    

    p.s. I'm not familiar with Swift.