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

如何更新NSTableView的行数以适配数据变化?

解决NSTableView行数未更新导致的索引越界问题

问题根源

你的代码中,Coordinator持有AppKitReferenceTable(值类型struct)的实例,当SwiftUI数据源更新时,AppKitReferenceTable会生成新实例,但Coordinator仍持有旧的struct副本,可能导致数据源与表格行数不同步;同时reloadData()的执行时机和数据更新存在异步差异,引发表格请求超出数组长度的行索引。

解决方案

  1. 让Coordinator直接绑定数据源:替代持有父struct实例,改为持有@Binding类型的数据源,确保始终获取最新数据。
  2. 增加索引安全检查:在生成表格视图时,提前判断行索引是否在数组范围内,避免崩溃。
  3. 确保主线程刷新表格:强制在主线程调用reloadData(),避免跨线程操作UI导致的状态不一致。

修改后的代码

AppKitReferenceTable.swift

import SwiftUI

struct AppKitReferenceTable: NSViewRepresentable {
    @Binding var data: [Entry]

    func makeCoordinator() -> Coordinator {
        // 直接传入数据源的Binding,而非父struct实例
        Coordinator(data: $data)
    }

    func makeNSView(context: Context) -> NSScrollView {
        let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TitleColumn"))
        column.width = 100.0

        let tableView = NSTableView()
        tableView.headerView = nil
        tableView.addTableColumn(column)
        tableView.delegate = context.coordinator
        tableView.dataSource = context.coordinator

        let scrollView = NSScrollView()
        scrollView.documentView = tableView
        return scrollView
    }

    func updateNSView(_ nsView: NSScrollView, context: Context) {
        guard let tableView = nsView.documentView as? NSTableView else { return }
        // 确保在主线程刷新表格
        DispatchQueue.main.async {
            tableView.reloadData()
        }
    }
}

extension AppKitReferenceTable {
    class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate {
        @Binding var data: [Entry]

        // 初始化时接收数据源的Binding
        init(data: Binding<[Entry]>) {
            self._data = data
        }

        func numberOfRows(in tableView: NSTableView) -> Int {
            data.count
        }

        func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
            // 增加安全检查,避免索引越界
            guard row < data.count else {
                return NSTextField(string: "无效行")
            }
            
            let textField = NSTextField()
            textField.delegate = self
            textField.tag = row

            switch tableColumn?.identifier.rawValue {
            case "TitleColumn":
                textField.stringValue = data[row].title ?? "无标题"
            default:
                textField.stringValue = "未知列"
            }

            return textField
        }
    }
}

ContentView.swift(无需修改)

struct ContentView: View {
    // 数据源唯一可信来源
    @State private var items: [Entry] = [entry1, entry2, entry3]

    var body: some View {
        VStack {
            AppKitReferenceTable(data: $items)
        }
        .onAppear() {
            // 模拟新数据传入
            self.items = [entry4, entry5]
        }
    }
}

说明

  • 让Coordinator直接绑定@Binding数据源,确保表格获取的始终是最新的数组长度和数据内容。
  • 新增的索引检查作为最后一道防线,即使出现异常情况也不会导致App崩溃。
  • 主线程刷新表格保证UI操作符合AppKit的线程安全要求,避免异步更新带来的状态不一致问题。

内容的提问来源于stack exchange,提问作者Niek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:54:53