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 的评论
我正在使用基于视图的 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 的评论