在 macOS Big Sur 下使用交替行颜色的 NSTableView 出现视觉故障
Visual glitch with NSTableView using alternating row colors under macOS Big Sur
我正在使用 NSTableView
,usesAlternatingRowBackgroundColors
设置为 true
。
只要我
- 添加许多列,例如 15 和
- 将 Cell Spacing 高度设置为 > 0
table 显示了一个视觉故障,其中交替行的高度分布不均:
这仅在 macOS Big Sur 下发生。 macOS Mojave 和 macOS Catalina 运行良好。我使用最新的 Xcode 12.4.
尝试了几乎任何设置和样式的组合
我的ViewController
相当简单:
class ViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
for columnIndex in 0..<15 {
let tableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "\(columnIndex)"))
tableColumn.title = "CustomColumn \(columnIndex)"
tableColumn.width = 150
tableView.addTableColumn(tableColumn)
}
}
}
而Interface Builder中NSTableView
的配置也很无聊,除了调整单元格间距高度:
如果有人可以确认问题并可能分享任何解决方法,那就太好了。
找到演示项目
我在 MacOS 11.0 和 MacOS 11.2.1 上测试了示例项目,但无法重现故障。但这并不意外,因为代码本身看起来不错。
可能是中级 MacOS 版本或特定硬件软件组合的问题。如果问题仍然存在,可以尝试通过继承 NSTableView
.
来手动绘制背景
class TableViewEx: NSTableView {
public override func drawBackground(inClipRect clipRect: NSRect) {
guard usesAlternatingRowBackgroundColors else {
super.drawBackground(inClipRect: clipRect)
return
}
backgroundColor.setFill()
clipRect.fill()
let effectiveRowHeight = rowHeight + self.intercellSpacing.height
// This color requires 10.14+. There is an older, deprecated property on NSColor to get this value if needed.
let altColor = NSColor.alternatingContentBackgroundColors.last ?? backgroundColor
altColor.setFill()
let rowIndex = Int((clipRect.minY / effectiveRowHeight).rounded(.down))
for i in rowIndex..<Int.max {
if i % 2 == 0 { continue }
let rect = NSRect(
x: clipRect.minX,
y: CGFloat(i) * effectiveRowHeight,
width: clipRect.width,
height: effectiveRowHeight
)
rect.fill()
if rect.maxY >= clipRect.maxY { break }
}
}
}
但即使在压倒性平局中它仍然存在,我也不会感到惊讶。我不认为 Cocoa 代码与提议的代码有什么不同,因此它也可能会在堆栈中呈现较低的错误。
对于那些选择在 ClipRect
中自己绘图的人,这是最好的方法。它有几个优点:
它仅在 tableView 的那部分绘制行,实际的 NSRowView
实例不会出现,因此效率更高。
当您滚动到 tableView 的“边缘”(边缘开始变成“橡皮筋”)时,现代 NSTableView 样式不会在“橡皮筋”中绘制任何东西带区”。这种方法遵循该约定。如果没有这个,当您拖离边缘时,ACTUAL table 行将停止绘制,但我们正在绘制的 FAKE table 行不会,这看起来很奇怪。
override func drawBackground(inClipRect clipRect: NSRect)
{
backgroundColor.setFill()
clipRect.fill()
let effectiveRowHeight: CGFloat = rowHeight + self.intercellSpacing.height
let altColor: NSColor = NSColor.alternatingContentBackgroundColors.last ?? backgroundColor
altColor.setFill()
// Don't draw rows that the tableView already has
let rowIndex = max(Int((clipRect.minY / effectiveRowHeight).rounded(.down)), numberOfRows)
// How many rows do we need to draw?
// Don't draw rows in the "rubber banding" zone if the user scrolls down past the end of the table, or horizontally off the side.
// That matches how modern NSTableView draws NSRowView instances, so the table will look uniform during "rubber-banding"
let maxRows = Int((self.bounds.size.height/effectiveRowHeight).rounded(.up))
let tableOrigin: CGFloat = bounds.origin.x
let tableWidth: CGFloat = bounds.size.width
guard rowIndex < maxRows else {
return
}
for i in rowIndex ..< maxRows
{
if i % 2 != 0
{
let rect = NSRect(x: tableOrigin, y: (CGFloat(i) * effectiveRowHeight), width: tableWidth, height: effectiveRowHeight)
rect.fill()
}
}
}
我在 WWDC'21 期间就此问题与 Apple 工程师进行了交谈,他确认了该问题并提出了一个有趣的解决方法:
override func drawBackground(inClipRect clipRect: NSRect)
{
super.drawBackground(inClipRect: clipRect)
}
这会触发一个不同的代码路径,在 AppKit 中进行较少的优化,并且还可以防止问题出现。
我还通过更多示例更新了我对 Apple 的反馈,看来这个问题终于在 macOS Monterey 12.0 Beta 5 (21A5304g) 中得到了解决。
我正在使用 NSTableView
,usesAlternatingRowBackgroundColors
设置为 true
。
只要我
- 添加许多列,例如 15 和
- 将 Cell Spacing 高度设置为 > 0
table 显示了一个视觉故障,其中交替行的高度分布不均:
这仅在 macOS Big Sur 下发生。 macOS Mojave 和 macOS Catalina 运行良好。我使用最新的 Xcode 12.4.
尝试了几乎任何设置和样式的组合我的ViewController
相当简单:
class ViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
for columnIndex in 0..<15 {
let tableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue: "\(columnIndex)"))
tableColumn.title = "CustomColumn \(columnIndex)"
tableColumn.width = 150
tableView.addTableColumn(tableColumn)
}
}
}
而Interface Builder中NSTableView
的配置也很无聊,除了调整单元格间距高度:
如果有人可以确认问题并可能分享任何解决方法,那就太好了。
找到演示项目我在 MacOS 11.0 和 MacOS 11.2.1 上测试了示例项目,但无法重现故障。但这并不意外,因为代码本身看起来不错。
可能是中级 MacOS 版本或特定硬件软件组合的问题。如果问题仍然存在,可以尝试通过继承 NSTableView
.
class TableViewEx: NSTableView {
public override func drawBackground(inClipRect clipRect: NSRect) {
guard usesAlternatingRowBackgroundColors else {
super.drawBackground(inClipRect: clipRect)
return
}
backgroundColor.setFill()
clipRect.fill()
let effectiveRowHeight = rowHeight + self.intercellSpacing.height
// This color requires 10.14+. There is an older, deprecated property on NSColor to get this value if needed.
let altColor = NSColor.alternatingContentBackgroundColors.last ?? backgroundColor
altColor.setFill()
let rowIndex = Int((clipRect.minY / effectiveRowHeight).rounded(.down))
for i in rowIndex..<Int.max {
if i % 2 == 0 { continue }
let rect = NSRect(
x: clipRect.minX,
y: CGFloat(i) * effectiveRowHeight,
width: clipRect.width,
height: effectiveRowHeight
)
rect.fill()
if rect.maxY >= clipRect.maxY { break }
}
}
}
但即使在压倒性平局中它仍然存在,我也不会感到惊讶。我不认为 Cocoa 代码与提议的代码有什么不同,因此它也可能会在堆栈中呈现较低的错误。
对于那些选择在 ClipRect
中自己绘图的人,这是最好的方法。它有几个优点:
它仅在 tableView 的那部分绘制行,实际的
NSRowView
实例不会出现,因此效率更高。当您滚动到 tableView 的“边缘”(边缘开始变成“橡皮筋”)时,现代 NSTableView 样式不会在“橡皮筋”中绘制任何东西带区”。这种方法遵循该约定。如果没有这个,当您拖离边缘时,ACTUAL table 行将停止绘制,但我们正在绘制的 FAKE table 行不会,这看起来很奇怪。
override func drawBackground(inClipRect clipRect: NSRect)
{
backgroundColor.setFill()
clipRect.fill()
let effectiveRowHeight: CGFloat = rowHeight + self.intercellSpacing.height
let altColor: NSColor = NSColor.alternatingContentBackgroundColors.last ?? backgroundColor
altColor.setFill()
// Don't draw rows that the tableView already has
let rowIndex = max(Int((clipRect.minY / effectiveRowHeight).rounded(.down)), numberOfRows)
// How many rows do we need to draw?
// Don't draw rows in the "rubber banding" zone if the user scrolls down past the end of the table, or horizontally off the side.
// That matches how modern NSTableView draws NSRowView instances, so the table will look uniform during "rubber-banding"
let maxRows = Int((self.bounds.size.height/effectiveRowHeight).rounded(.up))
let tableOrigin: CGFloat = bounds.origin.x
let tableWidth: CGFloat = bounds.size.width
guard rowIndex < maxRows else {
return
}
for i in rowIndex ..< maxRows
{
if i % 2 != 0
{
let rect = NSRect(x: tableOrigin, y: (CGFloat(i) * effectiveRowHeight), width: tableWidth, height: effectiveRowHeight)
rect.fill()
}
}
}
我在 WWDC'21 期间就此问题与 Apple 工程师进行了交谈,他确认了该问题并提出了一个有趣的解决方法:
override func drawBackground(inClipRect clipRect: NSRect)
{
super.drawBackground(inClipRect: clipRect)
}
这会触发一个不同的代码路径,在 AppKit 中进行较少的优化,并且还可以防止问题出现。
我还通过更多示例更新了我对 Apple 的反馈,看来这个问题终于在 macOS Monterey 12.0 Beta 5 (21A5304g) 中得到了解决。