Как реализовать NSTableViewRowlView как селектор объектов Xcode IB

Я пытаюсь реализовать NSTableView это похоже на селектор объектов Xcode IB (нижняя правая панель). Как показано ниже, когда выбран ряд, горизонтальная линия полной ширины рисуется выше и ниже выбранной строки.

Я успешно создал подкласс 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))")
            }

    }

Кажется странным, что ни один из них не вызывает перерисовку строки - я что-то здесь упускаю!

РЕДАКТИРОВАТЬ: ОК Я нашел что-то, что, кажется, работает OKish - все еще есть видимая задержка в перерисовке строки над невыбранной строкой, которой нет в табличном представлении XCode.

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.selectedRow в tableViewSelectionDidChange(:) метод.

Я думаю, возможно, что tableView нужно разделить на подклассы, чтобы убедиться, что обе строки перерисовываются в одном и том же цикле обновления.

1 ответ

Этот подкласс 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()
        }
    }
}
Другие вопросы по тегам