Create View Based Nstableview Programmatically Using Bindings in Swift

Create view based NSTableView programmatically using Bindings in Swift

I am answering my own question. Note that I am a beginner and do not know if this is the right way to do things. As noted by user stevesilva in the comments of the above question, I had to implement the delegate method tableView:viewForTableColumn:row: to ensure that the table is view based. Within the delegate method I tried to create a NSTableCellView and bind the textField property but this did not work. I had to subclass NSTableCellView, create a new text field property and then bind that property. This is how my delegate eventually looked.

func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {

let frameRect = NSRect(x: 0, y: 0, width: tableColumn!.width, height: 20)

let tableCellView = MyTableCellView(frame: frameRect)

if tableColumn?.identifier == "name column" {
tableCellView.aTextField?.bind("value", toObject: tableCellView, withKeyPath: "objectValue.name", options: nil)
} else if tableColumn?.identifier == "raise column" {
tableCellView.aTextField?.bind("value", toObject: tableCellView, withKeyPath: "objectValue.raise", options: nil)
}

return tableCellView
}

And this is my subclassed NSTableCellView:

class MyTableCellView: NSTableCellView {

var aTextField: NSTextField?

override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
aTextField = NSTextField(frame: frameRect)
aTextField?.drawsBackground = false
aTextField?.bordered = false
self.addSubview(aTextField!)
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

}

Binding NSTableView to NSArrayController programmatically

These 2 lines:

let t = NSTextField(...)
t.bind(.value, to: t, withKeyPath: "objectValue.name", options: nil)

You are binding the text field to itself, on a non-existing key path. If you expand the view hierarchy of a table view, it goes like this:

Table View > Column > Cell View > Text Field

What you need to provide is the Cell View, with the text field nested inside.

fileprivate extension NSUserInterfaceItemIdentifier {
static let cellView = NSUserInterfaceItemIdentifier("CellView")
static let name = NSUserInterfaceItemIdentifier("NameColumn")
static let age = NSUserInterfaceItemIdentifier("AgeColumn")
}

extension ViewController: NSTableViewDelegate, NSTableViewDataSource {
private func makeCell(frame: CGRect) -> NSTableCellView {
// Reuse an existing cell if possible
if let existingCell = table.makeView(withIdentifier: .cellView, owner: nil) as? NSTableCellView {
return existingCell
}

// Make a new cell
let textField = NSTextField(frame: frame)
textField.drawsBackground = false
textField.isBordered = false

let cell = NSTableCellView(frame: frame)
cell.identifier = .cellView
cell.textField = textField
cell.addSubview(textField)
return cell
}

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let column = tableColumn else { return nil }

let cell = makeCell(frame: NSRect(x: 0, y: 0, width: column.width, height: table.rowHeight))

// Varying the binding based on what column is being requested
switch column.identifier {
case .name:
cell.textField?.bind(.value, to: cell, withKeyPath: "objectValue.name", options: nil)
case .age:
cell.textField?.bind(.value, to: cell, withKeyPath: "objectValue.age", options: nil)
default:
print("Unrecognized column '\(column.identifier)'")
}
return cell
}
}

View-based table bindings in Swift

It sounds like you've failed to implement the delegate method -tableView:viewForTableColumn:row:. That's a common cause of blank tables.

You don't actually have to implement that if you make sure the identifier of the table column and the identifier of the table cell view are the same in IB.

Otherwise, the method can just do this:

- (NSView*) tableView:(NSTableView*)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row
{
return [tableView makeViewWithIdentifier:@"TheIdentifierOfYourTableCellView" owner:self];
}

(It could also do more, if desired.)

NSPopupButton in view based NSTableView: getting bindings to work

I had the same problem. I've put a sample project showing this is possible on Github.

Someone suggested a workaround using an IBOutlet property to the Authors
array controller but this doesn't seem to work for me either.

This is the approach that did work for me, and that is demonstrated in the sample project. The missing bit of the puzzle is that that IBOutlet to the array controller needs to be in the class that provides the TableView's delegate.

View-Based NSTableView Scrolling Performance

The issue seems to be related to the performClick() function on NSStatusItem's button property, and GCD. If I simply assign a menu to my NSStatusItem (and thus open it with any click (i.e. left-click or right-click), the scrolling is buttery smooth. If I don't use GCD to call the performClick() function, scrolling is also buttery smooth.**

JACKED-UP CLUNKY SCROLLING:

@objc func statusItemClicked(_ sender: Any?)
{
NSApp.activate(ignoringOtherApps: true)

if (NSApp.currentEvent != nil)
{
print("right click")
let event = NSApp.currentEvent!

if event.type == NSEvent.EventType.rightMouseUp || event.modifierFlags.contains(.control) // RIGHT-CLICK
{


// THIS IS THE PROBLEM:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01)
{
self.statusItem.menu = self.statusItemMenu
self.statusItem.button?.performClick(nil)
}
// END PROBLEM


}
else // LEFT-CLICK
{
print("left click")
}
}




BUTTERY-SMOOTH SCROLLING:

@objc func statusItemClicked(_ sender: Any?)
{
NSApp.activate(ignoringOtherApps: true)

if (NSApp.currentEvent != nil)
{
print("right click")
let event = NSApp.currentEvent!

if event.type == NSEvent.EventType.rightMouseUp || event.modifierFlags.contains(.control) // RIGHT-CLICK
{
self.statusItem.menu = self.statusItemMenu
self.statusItem.button?.performClick(nil)
}
else // LEFT-CLICK
{
print("left click")
}
}


Related Topics



Leave a reply



Submit