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

纯代码实现NSTableView调用makeView时始终无法创建NSTableCellView

问题描述

纯代码实现NSTableView时,调用tableView.makeView(withIdentifier:owner:)始终返回nil,导致无法显示单元格数据。表格能正常显示列标题、可导航30行,但数据无法渲染,已确认代理方法tableView(_:viewFor:row:)被正确调用,不想使用绑定方式解决。

代码示例
import Cocoa

class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
    var students: [Student] = []

    var tableView = NSTableView()
    var studentIdColumn = NSTableColumn(identifier: .init("student_id_column"))
    var nameColumn = NSTableColumn(identifier: .init("student_name_column"))
    var gpaColumn = NSTableColumn(identifier: .init("student_gpa_column"))
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        students = createStudents(numberOfStudents: 30)
        
        configureUI()
    }
    
    private func configureUI() {
        configureTable()
        view.addSubviews([ tableView ])
        configureConstraints()
    }
    
    private func configureTableColumns() {
        studentIdColumn.width = 125
        gpaColumn.width = 125
        nameColumn.width = 550
        
        studentIdColumn.title = "Student ID"
        nameColumn.title = "Student Name"
        gpaColumn.title = "GPA"
        
        tableView.addTableColumn(studentIdColumn)
        tableView.addTableColumn(nameColumn)
        tableView.addTableColumn(gpaColumn)
    }
    
    private func configureTable() {
        configureTableColumns()
        
        tableView.delegate = self
        tableView.dataSource = self
    }
    
    private func configureConstraints() {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
    }
    
    private func createStudents(numberOfStudents: Int) -> [Student] {
        let firstNames = Constants.firstNames
        let lastNames = Constants.lastNames

        var students = [Student]()
        
        for _ in 0..<numberOfStudents {
            let studentId = generateStudentId()
            let firstName = firstNames[Int.random(in: 0..<firstNames.count)]
            let lastName = lastNames[Int.random(in: 0..<lastNames.count)]
            let gpa = generateGPA()
            
            let s = Student(studentId: studentId, name: "\(firstName) \(lastName)", gpa: gpa)
            
            students.append(s)
        }
        
        return students
    }
    
    private func generateStudentId() -> String {
        var studentId = ""
        var isDuplicate = false
        
        repeat {
            studentId = String(Int.random(in: 100000..<1000000))
            isDuplicate = students.contains { student in
                student.studentId == studentId
            }
        } while isDuplicate
        
        return studentId
    }
    
    private func generateGPA() -> Double {
        return Double.random(in: 0..<5)
    }
    
    func numberOfRows(in tableView: NSTableView) -> Int {
        return students.count
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard let cellView = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) else { return nil }
        
        if tableColumn!.title == "Student ID" {
            cellView.textField?.stringValue = students[row].studentId
        } else if tableColumn!.title == "Student Name" {
            cellView.textField?.stringValue = students[row].name
        } else {
            cellView.textField?.stringValue = getGPAFormatter().string(from: NSNumber(value: students[row].gpa))!
        }
        
        return cellView
    }
    
    private func getGPAFormatter() -> NumberFormatter {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.minimumFractionDigits = 2
        formatter.maximumFractionDigits = 2
        
        return formatter
    }
}
解决方向
  • 注册单元格视图类:NSTableView不会自动创建未注册的单元格类型,必须提前为每个列的identifier注册对应的视图类。修改configureTable方法,添加注册逻辑:
    private func configureTable() {
        configureTableColumns()
        
        // 注册单元格视图
        tableView.register(NSTableCellView.self, forIdentifier: studentIdColumn.identifier)
        tableView.register(NSTableCellView.self, forIdentifier: nameColumn.identifier)
        tableView.register(NSTableCellView.self, forIdentifier: gpaColumn.identifier)
        
        tableView.delegate = self
        tableView.dataSource = self
    }
    
  • 优化可选值处理:避免强制解包引发崩溃,修改代理方法中的可选绑定逻辑:
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard let tableColumn = tableColumn,
              let cellView = tableView.makeView(withIdentifier: tableColumn.identifier, owner: self) as? NSTableCellView else {
            return nil
        }
        
        switch tableColumn.identifier.rawValue {
        case "student_id_column":
            cellView.textField?.stringValue = students[row].studentId
        case "student_name_column":
            cellView.textField?.stringValue = students[row].name
        case "student_gpa_column":
            cellView.textField?.stringValue = getGPAFormatter().string(from: NSNumber(value: students[row].gpa)) ?? "0.00"
        default:
            break
        }
        
        return cellView
    }
    
  • 设置行高(可选):如果单元格内容显示不全,可自定义行高或开启自动行高:
    // 在configureTable中添加
    tableView.rowHeight = 28 // 固定行高
    // 或开启自动行高
    tableView.usesAutomaticRowHeight = true
    tableView.rowHeight = NSTableView.automaticDimension
    tableView.minimumRowHeight = 28
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:07:32