0

我正在尝试实现一个NSTableView看起来类似于 Xcode IB 对象选择器(右下面板)的。As shown below when a row is selected a full width horizo​​ntal line is draw above and below the selected row.

我已经成功创建了一个子类,NSTableRowView并使用了 isNextRowSelected 属性来确定是否绘制全宽分隔符,这几乎可以工作。

问题是所选行上方的行没有被重绘,除非您碰巧选择了一行,然后立即选择了它下方的行。

如何有效地让 NSTableView 每次都重绘所选行上方的行?

在此处输入图像描述

这是我选择单行时的实现 在此处输入图像描述

如果现在选择了紧随其后的行 - 这就是我想要的。 在此处输入图像描述

/// This subclass draws a partial line as the separator for unselected rows and a full width line above and below for selected rows
/// |    ROW     |
/// | ---------- |  unselected separator

/// |------------|  selected separator on row above selected row
/// |    ROW     |
/// |------------|  selected separator
///
/// Issue: Row above selected row does not get redrawn when selected row is deselected
class OSTableRowView: NSTableRowView {

    let separatorColor  = NSColor(calibratedWhite: 0.35, alpha: 1)
    let selectedSeparatorColor  = NSColor(calibratedWhite: 0.15, alpha: 1)
    let selectedFillColor       = NSColor(calibratedWhite: 0.82, alpha: 1)


    override func drawSeparator(in dirtyRect: NSRect) {
        let yBottom = self.bounds.height
        let gap: CGFloat = 4.0
        let xLeft: CGFloat = 0.0
        let xRight = xLeft + self.bounds.width

        let lines = NSBezierPath()

        /// Draw a full width separator if the item is selected or if the next row is selected
        if self.isSelected || self.isNextRowSelected {
            selectedSeparatorColor.setStroke()
            lines.move(to: NSPoint(x: xLeft, y: yBottom))
            lines.line(to: NSPoint(x: xRight, y: yBottom))
            lines.lineWidth = 1.0
        } else {
            separatorColor.setStroke()
            lines.move(to: NSPoint(x: xLeft+gap, y: yBottom))
            lines.line(to: NSPoint(x: xRight-gap, y: yBottom))
            lines.lineWidth = 0.0
        }

        lines.stroke()
    }

    override func drawSelection(in dirtyRect: NSRect) {
        if self.selectionHighlightStyle != .none {
            let selectionRect = self.bounds
            selectedSeparatorColor.setStroke()
            selectedFillColor.setFill()
            selectionRect.fill()
        }
    }
}

在阅读了其他几篇文章后,我尝试添加代码以重绘前一行。这似乎没有效果。

func selectionShouldChange(in tableView: NSTableView) -> Bool {
        let selection = tableView.selectedRow
        if selection > 0 {
            tableView.setNeedsDisplay(tableView.rect(ofRow: selection-1))
            tableView.displayIfNeeded()
        }
        return true
    }

这也不是。

func tableViewSelectionDidChange(_ notification: Notification) {
        guard let tableView = self.sidebarOutlineView else {
            return
        }
        let row = tableView.selectedRow
            if row > 0 {
                tableView.setNeedsDisplay(tableView.rect(ofRow: row-1))
                print("row-1 update rect: \(tableView.rect(ofRow: row-1))")
            }

    }

似乎很奇怪,这些都没有触发重绘行 - 我在这里遗漏了什么!

编辑:好的,我发现一些似乎可以正常工作的东西 - 在 XCode tableView 中不存在的取消选择的行上方的行的重绘中仍然存在明显的滞后。

var lastSelectedRow = -1 {
        didSet {
            guard let tableView = self.sidebarOutlineView else {
                return
            }
            if oldValue != lastSelectedRow {

                if oldValue > 0 {
                    if let view = tableView.rowView(atRow: oldValue-1, makeIfNecessary: false) {
                        view.needsDisplay = true
                    }
                }
                if lastSelectedRow > 0 {
                    if let view = tableView.rowView(atRow: lastSelectedRow-1, makeIfNecessary: false) {
                        view.needsDisplay = true
                    }
                }
            }
        }
    }

然后简单地lastSelectedRow = tableView.selectedRowtableViewSelectionDidChange(:)方法中设置变量的值。

我认为可能需要对 tableView 进行子类化,以确保在同一更新周期中重绘两行。

4

1 回答 1

0

这个 NSTableRowView 子类似乎工作正常,在重绘上面的行时没有明显的滞后。

解决方案是每次都正确覆盖isSelected并设置在上面的行上。needsDisplay

/// This subclass draws a partial line as the separator for unselected rows and a full width line above and below for selected rows
/// |    ROW     |
/// | ---------- |  unselected separator

/// |------------|  selected separator on row above selected row
/// |    ROW     |
/// |------------|  selected separator
///
/// Issue: Row above selected row does not get redrawn when selected row is deselected
class OSTableRowView: NSTableRowView {

    let separatorColor  = NSColor(calibratedWhite: 0.35, alpha: 1)
    let selectedSeparatorColor  = NSColor(calibratedWhite: 0.15, alpha: 1)
    let selectedFillColor       = NSColor(calibratedWhite: 0.82, alpha: 1)

    /// Override this and whenever it is changed set the previous row to be updated
    override var isSelected: Bool {
        didSet {
            if let tableView = self.superview as? NSTableView {
                let row = tableView.row(for: self)
                if row > 0 {
                    tableView.rowView(atRow: row-1, makeIfNecessary: false)?.needsDisplay = true
                }
            }
        }
    }


    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
    }

    override func drawSeparator(in dirtyRect: NSRect) {
        let yBottom = self.bounds.height
        let gap: CGFloat = 4.0
        let xLeft: CGFloat = 0.0
        let xRight = xLeft + self.bounds.width

        let lines = NSBezierPath()

        /// Draw a full width separator if the item is selected or if the next row is selected
        if self.isSelected || self.isNextRowSelected {
            selectedSeparatorColor.setStroke()
            lines.move(to: NSPoint(x: xLeft, y: yBottom))
            lines.line(to: NSPoint(x: xRight, y: yBottom))
            lines.lineWidth = 1.0
        } else {
            separatorColor.setStroke()
            lines.move(to: NSPoint(x: xLeft+gap, y: yBottom))
            lines.line(to: NSPoint(x: xRight-gap, y: yBottom))
            lines.lineWidth = 0.0
        }

        lines.stroke()
    }

    override func drawSelection(in dirtyRect: NSRect) {
        if self.selectionHighlightStyle != .none {
            let selectionRect = self.bounds
            selectedSeparatorColor.setStroke()
            selectedFillColor.setFill()
            selectionRect.fill()
        }
    }
}
于 2018-07-30T02:15:02.290 回答