如何更新编程创建的NSTableView数据源:编辑单元格后同步数据
解决视图型NSTableView单元格编辑后更新数据源的问题
核心思路
视图型NSTableView无法使用单元格型表格的tableView(_:setObjectValue:for:row:)方法,必须给单元格内的NSTextField绑定target-action,在编辑结束时触发自定义逻辑,更新数据源对应位置的数据。
具体实现步骤
1. 自定义单元格(推荐方案)
自定义单元格类来存储当前单元格对应的行、列索引,避免用tag或关联对象的繁琐操作:
class EditableTableCellView: NSTableCellView { // 存储当前单元格的行、列索引 var rowIndex: Int = 0 var columnIndex: Int = 0 override func awakeFromNib() { super.awakeFromNib() // 给文本框绑定编辑结束的触发事件 textField?.target = self textField?.action = #selector(textFieldEditFinished(_:)) } @objc private func textFieldEditFinished(_ textField: NSTextField) { // 通过通知把编辑信息传递给控制器 NotificationCenter.default.post( name: NSNotification.Name(rawValue: "TableCellEdited"), object: self, userInfo: [ "row": rowIndex, "column": columnIndex, "newValue": textField.stringValue ] ) } }
2. 配置单元格并绑定索引
在数据源方法中,给自定义单元格设置对应的数据和索引:
// 假设你的数据源是可变二维数组 var yourDataSource: [[String]] = [] func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { guard let columnIndex = tableView.tableColumns.firstIndex(of: tableColumn!) else { return nil } let cellID = NSUserInterfaceItemIdentifier("EditableCell") // 获取或创建自定义单元格 let cell = tableView.makeView(withIdentifier: cellID, owner: self) as? EditableTableCellView ?? EditableTableCellView(identifier: cellID) // 设置单元格内容 cell.textField?.stringValue = yourDataSource[row][columnIndex] // 绑定当前行、列索引 cell.rowIndex = row cell.columnIndex = columnIndex // 开启编辑权限 cell.textField?.isEditable = true return cell }
3. 监听编辑事件并更新数据源
在控制器中监听通知,处理数据源更新:
override func viewDidLoad() { super.viewDidLoad() // 监听单元格编辑完成的通知 NotificationCenter.default.addObserver( self, selector: #selector(updateDataSource(_:)), name: NSNotification.Name(rawValue: "TableCellEdited"), object: nil ) } @objc private func updateDataSource(_ notification: Notification) { guard let userInfo = notification.userInfo, let row = userInfo["row"] as? Int, let column = userInfo["column"] as? Int, let newValue = userInfo["newValue"] as? String else { return } // 检查索引合法性,防止越界崩溃 guard row < yourDataSource.count, column < yourDataSource[row].count else { return } // 更新数据源 yourDataSource[row][column] = newValue } // 控制器销毁时移除通知监听,避免内存泄漏 deinit { NotificationCenter.default.removeObserver(self) }
替代方案:直接绑定控制器(无需自定义单元格)
如果不想自定义单元格,可直接在数据源方法中给系统单元格的文本框绑定目标和动作,用关联对象存储行索引:
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { guard let columnIndex = tableView.tableColumns.firstIndex(of: tableColumn!) else { return nil } let cellID = NSUserInterfaceItemIdentifier("EditableCell") let cell = tableView.makeView(withIdentifier: cellID, owner: self) as? NSTableCellView ?? NSTableCellView(identifier: cellID) cell.textField?.stringValue = yourDataSource[row][columnIndex] cell.textField?.isEditable = true // 绑定目标为当前控制器,动作为自定义方法 cell.textField?.target = self cell.textField?.action = #selector(onTextFieldEdited(_:)) // 列索引存在tag,行索引用关联对象绑定到文本框 cell.textField?.tag = columnIndex objc_setAssociatedObject(cell.textField!, "rowIndex", row, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) return cell } @objc private func onTextFieldEdited(_ textField: NSTextField) { guard let row = objc_getAssociatedObject(textField, "rowIndex") as? Int, let column = textField.tag as? Int else { return } guard row < yourDataSource.count, column < yourDataSource[row].count else { return } yourDataSource[row][column] = textField.stringValue }
注意事项
- 确保
yourDataSource是可变数组(用var声明),不可变数组无法直接修改元素。 - 必须处理索引越界判断,避免因数据源变化或异常操作导致崩溃。
- 使用通知方案时,务必在控制器销毁时移除监听,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Cherub
相关产品推荐
相关产品推荐

