Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scroll an NSTableView so that a row is centered

I want to programmatically scroll an NSTableView so that a particular row is centered. It's simple to scroll the NSTableView so that a particular row is visible:

[theTableView scrollRowToVisible:pos];

However, usually the row in question is at the bottom of the visible area, whereas I'd like it to be roughly in the center.

Another stupid approach is to scroll a few rows beyond the one I want to be visible, e.g., something like:

    // pos = index of desired row
    // numRows = number of rows in the table
    NSRect visibleRect = [resultsTableView visibleRect];
    NSRange visibleRange = [resultsTableView rowsInRect:visibleRect];
    NSUInteger offset = visibleRange.length/2;
    NSUInteger i;
    if (pos + offset >= numRows)
        i = numRows - 1;
    else if (pos < visibleRange.length)
        i = pos;
    else
        i = pos + offset;
    [resultsTableView scrollRowToVisible:i];

This works if all rows are exactly the same height, but I am interested in making rows with different heights.

Is there a better, perhaps more direct, way to do this? For example, I have noticed that the NSTableView is wrapped in an NSScrollView.... (The table was made using Interface Builder.)

Thanks!

like image 872
N F Avatar asked Aug 01 '12 21:08

N F


3 Answers

This works for me on a sub-class of your NSTableView

func scrollRowToVisible(row: Int, animated: Bool) {

    if animated {
        guard let clipView = superview as? NSClipView,
              let scrollView = clipView.superview as? NSScrollView else {

                assertionFailure("Unexpected NSTableView view hiearchy")
                return
        }

        let rowRect = rect(ofRow: row)
        var scrollOrigin = rowRect.origin

        let tableHalfHeight = clipView.frame.height * 0.5
        let rowRectHalfHeight = rowRect.height * 0.5

        scrollOrigin.y = (scrollOrigin.y - tableHalfHeight) + rowRectHalfHeight

        if scrollView.responds(to: #selector(NSScrollView.flashScrollers)) {
            scrollView.flashScrollers()
        }

        clipView.animator().setBoundsOrigin(scrollOrigin)

    } else {

        scrollRowToVisible(row)
    }
}

then you can call:

self.tableView.scrollRowToVisible(self.tableView.selectedRow, animated:true)

after you've selected the row

like image 129
Antonio de Perio Avatar answered Oct 16 '22 20:10

Antonio de Perio


I stumbled on this question while looking for a solution in Swift 4 (thanks to @AntoniodePerio for his answer that I ported):

extension NSTableView {
    func centreRow(row: Int, animated: Bool) {
        self.selectRowIndexes(IndexSet.init(integer: row), byExtendingSelection: false)
        let rowRect = self.frameOfCell(atColumn: 0, row: row)
        if let scrollView = self.enclosingScrollView {
            let centredPoint = NSMakePoint(0.0, rowRect.origin.y + (rowRect.size.height / 2) - ((scrollView.frame.size.height) / 2))
            if animated {
                scrollView.contentView.animator().setBoundsOrigin(centredPoint)
            } else {
                self.scroll(centredPoint)
            }
        }
    }
}

call with:

@IBOutlet weak var myTable: NSTableView! myTable.centreRow(row: 10, animated: true)

like image 20
Todd Avatar answered Oct 16 '22 19:10

Todd


I would take a look at NSView's -scrollPoint: and -scrollRectToVisible: methods. Try doing something like:

if (tableView.mainScrollView) {
    CGFloat yLocation = row.frame.origin.y + tableView.mainScrollView.contentView.frame.size.height / 2;
    if (yLocation > tableView.frame.size.height) yLocation = tableView.frame.size.height;
    NSPoint rowPoint = NSMakePoint(0, row.frame.origin.y + );
    [tableView scrollPoint:rowPoint];
}

I'm not sure if this works, since i can't test it, let me know.

like image 37
Vervious Avatar answered Oct 16 '22 21:10

Vervious