Как реализовать 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()
}
}
}