-1

I am new to Cocoa (and Swift) and am trying to wrap my head around a (View based) NSTableView, NSTableViewDataSource, NSTableViewDelegate, Storyboards, Notifications, etc. I am trying to create an NSTableView with a varying amount of columns (and rows). My header is a [String], my rows are a [[String]] (though if it's simpler for identification purposes, it could also be an [[String: String]]). There's a lot of tutorials/SO questions out there covering similar topics, so generating the table programmatically has not been an issue. However, I cannot figure out how to get notified to store data back into my [[String]] once a user edits a cell.

Please realise that I have probably looked at every post covering this topic on the Apple forums, SO, and others. If marking this question as a duplicate, please tell me which part of your link I have to look at and how I can adapt it for my purposes.

Currently my code is pretty much Robert's answer on this SO question with a couple of buttons added: one to print() my [[String]] datasource, and another to call his setContent() function. I have also added cell.textField?.isEditable = true to tableView(_:viewFor:row:) to make the cells editable.

I have found some answers pointing to tableView(_:setObjectValue:for:row:), however that never seems to get called - I assume because as per Apple's docs that is meant for Cell based NSTableViews, not View based NSTableViews. That page says "In view-based tables, use target/action to set each item in the view cell.", but does not go into further detail on how to implement this.

This seems very close but it's in Obj-C and I find difficult to adapt for Swift.

Cherub
  • 17
  • 4
  • The "This" link is the answer to your question. Try to understand what the Objective-C code is doing (setting the target and action of the text field) and do the same in Swift. See also the linked question in that answer. If you're having trouble implementing this then post the code you tried please. – Willeke May 27 '23 at 20:24
  • Thanks for your quick reply, and confirming that link actually held the information I was looking for. I've figured it out now, and will answer/update my question with the solution in a bit. – Cherub May 27 '23 at 21:08

1 Answers1

1

I ended up changing the tableView(_:viewFor:row:) function to include cell.textField?.target = self and cell.textField?.action = #selector(self.onEditCell(_:)), like so:

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
    let column = tableView.tableColumns.firstIndex(of: tableColumn!)!
    tableColumn?.headerCell.title = header[column];

    if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as? NSTableCellView {
        cell.textField?.stringValue = list[row][column]
        cell.textField?.isEditable = true
        cell.textField?.target = self                           // <-- added
        cell.textField?.action = #selector(self.onEditCell(_:)) // <-- added
        return cell
    }

    return nil
}

and adding the function:

@IBAction func onEditCell(_ sender: Any) {
    let row: Int = tableView.row(for: sender as! NSView)
    let col: Int = tableView.column(for: sender as! NSView)
    let content: String = (sender as? NSTextField)?.stringValue ?? ""

    print("Col: \(col), row: \(row), value: \(content)")
}
Cherub
  • 17
  • 4
  • A bit late to the party, but yep, this is on the right track! With a tableview, you basically have two options: if the cells are simple enough, you can make the table view responsible for setting all the cell content from the model, and responding to all edits to the cells by making edits to the model directly. If your cells get more complicated, you can make your table more like a dumb container (like an NSWindow), where you use a use a custom subclass of `NSTableCellView`, giving each of them their own model object to read from, that they can edit themselves. – Alexander Jun 14 '23 at 01:32
  • 1
    Just some suggestions: 1) These calls to `cell.textField?.foo` are a risk. If you misconfigured your cells so they didn't have a text field, all these calls would fail silently by just doing nothing. In this case, it's better to just force unwrap, to bring your attention to the problem. 2) If you only use your action with text fields (and don't reuse it with say, buttons), you can strongly type your sender: `onEditCell(_ sender: NSTextField)`, which removes the need for that `(sender as? NSTextField)` cast. 3) Even if you did choose to keep the cast, `as!` would be preferably over `as?` here – Alexander Jun 14 '23 at 01:34