如何使用NSTableViewDiffableDataSource为NSTableView加载数据
问题描述
我正在学习使用NSTableViewDiffableDataSource为NSTableView加载数据。我能参考网上的示例在iOS中使用UITableViewDiffableDataSource和UICollectionViewDiffableDataSource加载数据,但在Cocoa中使用NSTableViewDiffableDataSource时始终失败。
我的场景如下:我定义了一个NSTableCellView的子类TestTableCellView,用来展示三个字段:名字、姓氏和字符串格式的出生日期。
import Cocoa class ViewController: NSViewController { // MARK: - Variables var dataSource: NSTableViewDiffableDataSource<Int, Contact>? // MARK: - IBOutlet @IBOutlet weak var tableView: NSTableView! // MARK: - Life cycle override func viewWillAppear() { super.viewWillAppear() let model1 = Contact(id: 1, firstName: "Christopher", lastName: "Wilson", dateOfBirth: "06-02-2001") let model2 = Contact(id: 2, firstName: "Jen", lastName: "Psaki", dateOfBirth: "08-25-1995") let model3 = Contact(id: 3, firstName: "Pete", lastName: "Marovich", dateOfBirth: "12-12-2012") let model4 = Contact(id: 4, firstName: "Deborah", lastName: "Mynatt", dateOfBirth: "11-08-1999") let model5 = Contact(id: 5, firstName: "Christof", lastName: "Kreb", dateOfBirth: "01-01-2001") let models = [model1, model2, model3, model4, model5] dataSource = NSTableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, tableColumn, row, identifier in let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: self) as! TestTableCellView let model = models[row] cell.firstField.stringValue = model.firstName cell.lastField.stringValue = model.lastName cell.dobField.stringValue = model.dateOfBirth return cell }) tableView.dataSource = dataSource guard let dataSource = self.dataSource else { return } var snapshot = dataSource.snapshot() snapshot.appendSections([0]) snapshot.appendItems(models, toSection: 0) dataSource.apply(snapshot, animatingDifferences: true, completion: nil) // <--- 此处崩溃... } } struct Contact: Hashable { var id: Int var firstName: String var lastName: String var dateOfBirth: String }
应用崩溃报错为Invalid parameter not satisfying: snapshot.。我之前测试另一个示例时也在dataSource.apply这一行崩溃。网上关于NSTableViewDiffableDataSource的示例很少,找到的相关内容也无法解决我的问题。我使用的Xcode版本是13.1,请问哪里操作有误?
问题原因&解决方案
- 错误1:首次构建快照时不应直接获取数据源的空快照
macOS 12对应SDK(Xcode13.x版本适配的SDK)存在已知bug,刚初始化的NSTableViewDiffableDataSource返回的空快照未通过内部校验,直接操作会触发崩溃。你需要直接创建全新的NSDiffableDataSourceSnapshot实例来构建数据。 - 错误2:cellProvider中错误使用本地数组索引取值
DiffableDataSource的cellProvider回调已经直接返回了当前行对应的模型对象(即第四个参数identifier),不需要通过row索引去本地数组拿值,后续数据发生变化、行顺序调整时索引取值会出现数据错位,甚至触发异常。 - 错误3:重复设置tableView的dataSource
初始化NSTableViewDiffableDataSource时传入tableView参数,系统已经自动将tableView的dataSource绑定到该数据源实例,重复赋值会在老版本SDK中引发未知问题。 - 错误4:首次加载开动画的适配问题
Xcode13.1对应SDK中,首次给空的tableView应用快照时,如果animatingDifferences设为true大概率会触发崩溃,首次加载建议设为false,后续增量更新时再开动画即可。 - 前置校验:确保NSTableColumn设置了正确的identifier
你需要在Storyboard/XIB中给tableView的每一列设置唯一的identifier,否则DiffableDataSource无法正确映射列和单元格,也会引发apply时崩溃。
修正后的核心代码
import Cocoa class ViewController: NSViewController { // MARK: - Variables var dataSource: NSTableViewDiffableDataSource<Int, Contact>? // MARK: - IBOutlet @IBOutlet weak var tableView: NSTableView! // MARK: - Life cycle override func viewWillAppear() { super.viewWillAppear() let model1 = Contact(id: 1, firstName: "Christopher", lastName: "Wilson", dateOfBirth: "06-02-2001") let model2 = Contact(id: 2, firstName: "Jen", lastName: "Psaki", dateOfBirth: "08-25-1995") let model3 = Contact(id: 3, firstName: "Pete", lastName: "Marovich", dateOfBirth: "12-12-2012") let model4 = Contact(id: 4, firstName: "Deborah", lastName: "Mynatt", dateOfBirth: "11-08-1999") let model5 = Contact(id: 5, firstName: "Christof", lastName: "Kreb", dateOfBirth: "01-01-2001") let models = [model1, model2, model3, model4, model5] dataSource = NSTableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, tableColumn, row, identifier in let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "cell"), owner: self) as! TestTableCellView // 直接用回调返回的identifier取值,不要用本地数组索引 let model = identifier as! Contact cell.firstField.stringValue = model.firstName cell.lastField.stringValue = model.lastName cell.dobField.stringValue = model.dateOfBirth return cell }) guard let dataSource = self.dataSource else { return } // 新建快照,不要从dataSource取空快照 var snapshot = NSDiffableDataSourceSnapshot<Int, Contact>() snapshot.appendSections([0]) snapshot.appendItems(models, toSection: 0) // 首次加载animatingDifferences设为false dataSource.apply(snapshot, animatingDifferences: false, completion: nil) } } struct Contact: Hashable { var id: Int var firstName: String var lastName: String var dateOfBirth: String }
内容的提问来源于stack exchange,提问作者El Tomato
相关产品推荐
相关产品推荐

