【问题标题】:Visual glitch with NSTableView using alternating row colors under macOS Big Sur在 macOS Big Sur 下使用交替行颜色的 NSTableView 的视觉故障
【发布时间】:2021-05-07 10:43:06
【问题描述】:

我正在使用 NSTableView 并将 usesAlternatingRowBackgroundColors 设置为 true

只要我

  1. 添加许多列,例如,15
  2. 将单元格间距高度设置为 > 0

表格显示了一个视觉故障,其中交替的行在高度上分布不均:

这仅在 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的配置也比较枯燥,除了调整单元格间距高度:

如果有人可以确认问题并可能分享任何解决方法,那就太好了。

你可以在https://github.com/fheidenreich/table-test找到一个演示项目

【问题讨论】:

  • 我没有答案,但我可以确认你不是唯一看到这个的人。在一个有 26 列的应用程序中,我也发现了这些故障。我自己画了cliprect背景。

标签: swift nstableview appkit macos-big-sur


【解决方案1】:

我在 WWDC'21 期间就这个问题与一位 Apple 工程师进行了交谈,他确认了这个问题并提出了一个有趣的解决方法:

override func drawBackground(inClipRect clipRect: NSRect)
{
   super.drawBackground(inClipRect: clipRect)
}

这会触发 AppKit 内部优化较少的不同代码路径,并防止问题出现。

我还通过更多示例更新了我对 Apple 的反馈,看来问题最终在 macOS Monterey 12.0 Beta 5 (21A5304g) 中得到解决。

【讨论】:

  • 好吧,我可以确认这是可行的。但这意味着 -drawBackground 的正常实现是在进行自省,而这绝不应该在绘图方法中完成。它被一遍又一遍地锤击。我有一些问题要问 Corbin Dunn 和团队。
【解决方案2】:

对于那些选择在ClipRect 中自己绘图的人,这是最好的方法。它有几个优点:

  1. 它仅在 tableView 的实际NSRowView 实例不出现的那部分绘制行,因此效率更高。

  2. 当您滚动 tableView 的“边缘”时(使边缘开始“橡皮筋”),现代 NSTableView 样式不会在“橡皮筋区域”中绘制任何内容。这种方法遵循该约定。如果没有这个,ACTUAL 表格行将在您拖出边缘时停止绘制,但我们正在绘制的 FAKE 表格行不会,这看起来很奇怪。

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()
        }
    }
}

【讨论】:

  • 谢谢@Bryan — 您的回答确实考虑了表格视图的大部分行为。请参阅我的答案以获得更本地但不太明显的解决方法。
【解决方案3】:

我在 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 代码与提议的代码有什么不同,因此它可能会在堆栈中呈现较低的错误。

【讨论】:

  • 感谢塞巴斯蒂安!没想到会在这里见到你 :) 你的解决方法启动了有趣的实验。请参阅我的答案以获得更原生但不太明显的解决方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-08
  • 2022-01-09
  • 1970-01-01
  • 2021-09-07
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多