NSTableView.setNeedsDisplay() 仅在附加的格式化程序更改时不重绘

NSTableView.setNeedsDisplay() not redrawing on attached Formatter changes only

我正在使用基于视图的 NSTableView,其中包含显示日期的列,并且 table 单元格视图使用共享的 DateFormatter。

let view: NSTableCellView? = tableView.makeView(withIdentifier: column.identifier, owner: self) as! NSTableCellView?
let entry = (logController.arrangedObjects as! [LogEntry])[row]
    switch column.identifier {
    case columnDateKey:
        view?.textField?.formatter = sharedDateFormatter
        view?.textField?.objectValue = entry.date

该应用程序有一个用户偏好来选择日期格式和以前的代码

tableView.setNeedsDisplay(tableView.rect(ofColumn: tableView.column(withIdentifier: columnDateKey)))

将使用新的日期格式刷新该列。

对于 macOS Mojave,这不会发生。调查表明,尽管为底层 TableView 调用了 drawRect:,但没有调用 tableView(:viewFor:row:) 来获取 table 单元格视图的新值.调用 tableView.reloadData(forRowIndexes:columnIndexes:) 确实会导致调用 tableView(:viewFor:row:) 但显示不会刷新(尽管它会刷新 tableView.reloadData()).

重绘的任何外部原因,例如选择一行正确地单独更新该区域。我看到的另一件事是,长时间 table 缓慢向上滚动最终会导致新格式出现,尽管现有单元格在滚动回时不会改变,直到在返回之前滚动很长一段路。这似乎可以推断,只有附加的格式化程序的配置发生变化时,缓存的视图才被认为没有发生变化(尽管内容的值发生变化时)

这种行为随着 Mojave 的引入而改变,我发现很难相信没有其他人报告过它,因此我开始质疑我的原始代码。我错过了什么吗?

下面的测试代码演示了这个问题,"View requested" 消息不会为 setNeedsDisplay 的变体打印,显示只会为 reloadData()

重绘

styleButton 是用于切换数字格式的复选框,refreshButton 是用于请求重绘的操作按钮

将值设置为随机值将导致预期的重绘行为

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    @IBOutlet weak var table: NSTableView!
    @IBOutlet weak var styleButton: NSButton!
    @IBOutlet weak var refreshButton: NSButton!
    @IBOutlet weak var testView: NSView!

    let numberFormatter = NumberFormatter()

    func applicationWillFinishLaunching(_ notification: Notification) {
    numberFormatter.numberStyle = symbolButton.state == NSControl.StateValue.on ? NumberFormatter.Style.decimal : NumberFormatter.Style.none
    }

    @IBAction func refresh(sender: Any?) {
        numberFormatter.numberStyle = styleButton.state == NSControl.StateValue.on ? NumberFormatter.Style.decimal : NumberFormatter.Style.none
        table.setNeedsDisplay(table.rect(ofColumn: 0))
        // table.needsDisplay = true
        // table.reloadData(forRowIndexes: IndexSet(integersIn: 0..<table.numberOfRows), columnIndexes:[0])
        // table.reloadData()
    }
}

extension AppDelegate: NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        if tableView == table {
            return 40
        }
        return 0
    }
}

extension AppDelegate: NSTableViewDelegate {
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        print("View requested")
        guard tableColumn != nil else {
            return nil
        }
        let column = tableColumn!
        if tableView == table {
            let view: NSTableCellView? = tableView.makeView(withIdentifier: column.identifier, owner: self) as! NSTableCellView?
            view?.textField?.formatter = numberFormatter
            view?.textField?.objectValue = 123.456
            return view
        }
        return nil
    }
}

错误地依赖view.setNeedsDisplay 来自动更新子视图。事实并非如此(尽管以前似乎是这样工作的)-请参阅上面 Willeke 的评论