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
Swift: When Should I Use "Var" Instead of "Let"
Gcd with Static Functions of a Struct
How to Mimic 'Uitableviewcontroller' Showing of the Large Titles in 'Navigationbar' on iOS 11
Appending Tuples to an Array of Tuples
How to Stream Remote Audio in iOS 13? (Swiftui)
Swift Utf8 Encoding and Non Utf8 Character
Get Signed Integer from Swift String of Binary
Multi-Face Detection in Realitykit
Swift 2 to 3 Migration Dispatch_Get_Global_Queue
Nsdatecomponents Weekofyear Returns Wrong Date
How to Pass Int.Init to a Function in Swift
How to Convert Uint16 to Uint8 in Swift 3
Swift UI MACos Background Transparent Textfield
Load Desktop Version Wkwebview iOS 9
Controlling Size of Texteditor in Swiftui