You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 19:45:07