2
votes

I have a view based NSTableView and can't figure out how to work around a visual glitch where the currently selected row flickers while scrolling up or down with the arrow keys.

The selected row should appear 'glued' to either the top or bottom of the view, depending on scroll direction. The Finder shows this correct behavior in list view but a regular table view seems to not behave this way out of the box. I'm confused as to why that is and see no obvious way to circumvent it. Can anybody point me to possible causes / solutions?

Edit No. 1

A cell based NSTableView behaves in the desired way by default, so this is presumably a bug specific to the view based implementation. I don't want to use a cell based table for unrelated reasons though.

Edit No. 2

I've tried making the table view's parent view layer backed, as well as intercepting the up / down arrow keystrokes to do my own scrolling, but so far I haven't been able to eliminate the flickering.

Edit No. 3

I've created a small sample project that reproduces the issue.

2
It would really help if you gave us a minimal app that does reproduce the problem. Put it in GitHub and give us a link to it.jvarela
@jvarela I added a sample project that shows the problem.Marcel Tesch
I was able to reproduce your bug. Actually even my own table code shows that bug too, which I had never noticed. I even checked in Apple's sample code and they have the same problem. Perhaps the only way to solve the problem would be to customize the way the selection is drawn. I tried to customize this drawing by overriding NSTableRowView drawSelection(in:) but using NSRect fill() method results in the same flickering. Perhaps you should open a DTS incident and ask Apple how to solve this bug.jvarela
It flickers on Big Sur as well (beta 3).zrzka
It looks like the selection changes and the old and new selected rows are redrawn. Next the selected row is animated up/down. Disabling scroll animation fixes the issue.Willeke

2 Answers

3
votes

It looks like the selection changes and the old and new selected rows are redrawn. Next the selected row is animated up/down. Disabling scroll animation fixes the issue. Scroll animation can be disabled by subclassing NSClipView and overriding scroll(to:).

override func scroll(to newOrigin: NSPoint) {
    setBoundsOrigin(newOrigin)      
}

It might have some side effects.

Edit

Copied from zrzka's solution, with some adjustments. Scroll animation is temporarily disabled when using the up arrow or down arrow key.

class TableView: NSTableView {

    override func keyDown(with event: NSEvent) {
        if let clipView = enclosingScrollView?.contentView as? ClipView,
            (125...126).contains(event.keyCode) && // down arrow and up arrow
            event.modifierFlags.intersection([.option, .shift]).isEmpty {
            clipView.isScrollAnimationEnabled = false
            super.keyDown(with: event)
            clipView.isScrollAnimationEnabled = true
        }
        else {
            super.keyDown(with: event)
        }
    }
    
}

class ClipView: NSClipView {

    var isScrollAnimationEnabled: Bool = true
    
    override func scroll(to newOrigin: NSPoint) {
        if isScrollAnimationEnabled {
            super.scroll(to: newOrigin)
        } else {
            setBoundsOrigin(newOrigin)
            documentView?.enclosingScrollView?.flashScrollers()
        }
    }
    
}
0
votes

Did you try change the view ?

scrollView.wantsLayer = true

If you used Interface Builder:

Select the scroll view Open the View Effects Inspector (or press Cmd-Opt-8) In the table, find the row for your scroll view and check the box.