简体   繁体   中英

Tracking the position of a NSCell on change

I have a NSTableView and want to track the position of its containing NSCell s when the tableView got scrolled by the user.

I couldn't find anything helpful. Would be great if someone can lead me into the right direction!

EDIT:

Thanks to @Ken Thomases and @Code Different , I just realized that I am using a view-based tableView, using tableView(_ tableView:viewFor tableColumn:row:) , which returns a NSView.

However, that NSView is essentially a NSCell.

let cell = myTableView.make(withIdentifier: "customCell", owner: self) as! MyCustomTableCellView // NSTableCellView

So I really hope my initial question wasn't misleading. I am still searching for a way how to track the position of the individual cells/views .

I set the behaviour of the NSScrollView (which contains the tableView) to Copy on Scroll in IB. 在此处输入图片说明

But when I check the x and y of the view/cells frame (within viewWillDraw of my MyCustomTableCellView subclass) it remains 0, 0 .

Update based on edited question:

First, just so you're aware, NSTableCellView is not an NSCell nor a subclass of it. When you are using a view-based table, you are not using NSCell for the cell views.

Also, a view's frame is always relative to the bounds of its immediate superview. It's not an absolute position. And the superview of the cell view is not the table view nor the scroll view. Cell views are inside of row views. That's why your cell view's origin is at 0, 0.

You could use NSTableView 's frameOfCell(atColumn:row:) to determine where a given cell view is within the table view. I still don't think this is a good approach, though. Please see the last paragraph of my original answer, below:


Original answer:

Table views do not "contain" a bunch of NSCell s as you seem to think. Also, NSCell s do not have a position. The whole point of NSCell -based compound views is that they're much lighter-weight than an architecture that uses a separate object for each cell.

Usually, there's one NSCell for each table column. When the table view needs to draw the cells within a column, it configures that column's NSCell with the data for one cell and tells it to draw at that cell's position. Then, it configures that same NSCell with the data for the next cell and tells it to draw at the next position. Etc.

To do what you want, you could configure the scroll view to not copy on scroll. Then, the table view will be asked to draw everything whenever it is scrolled. Then, you would implement the tableView(_:willDisplayCell:for:row:) delegate method and apply the alpha value to the cells at the top and bottom edges of the scroll view.

But that's probably not a great approach.

I think you may have better luck by adding floating subviews to the scroll view that are partially transparent, with a gradient from fully opaque to fully transparent in the background color. So, instead of the cells fading out and letting the background show through, you put another view on top which only lets part of the cells show through.

NSScrollView doesn't use delegate. It uses the notification center to inform an observer that a change has taken place. The solution below assume vertical scrolling.

override func viewDidLoad() {
    super.viewDidLoad()

    // Observe the notification that the scroll view sends out whenever it finishes a scroll
    let notificationName = NSNotification.Name.NSScrollViewDidLiveScroll
    NotificationCenter.default.addObserver(self, selector: #selector(scrollViewDidScroll(_:)), name: notificationName, object: scrollView)

    // Post an intial notification to so the user doesn't have to start scrolling to see the effect
    scrollViewDidScroll(Notification(name: notificationName, object: scrollView, userInfo: nil))
}

// Whenever the scroll view finished scrolling, we will start coloring the rows
// based on how much they are visible in the scroll view. The idea is we will
// perform hit testing every n-pixel in the scroll view to see what table row
// lies there and change its color accordingly
func scrollViewDidScroll(_ notification: Notification) {
    // The data's part of a table view begins with at the bottom of the table's header
    let topEdge = tableView.headerView!.frame.height
    let bottomEdge = scrollView.bounds.height

    // We are going to do hit-testing every 10 pixel. For best efficiency, set
    // the value to your typical row's height
    let step = CGFloat(10.0)

    for y in stride(from: topEdge, to: bottomEdge, by: step) { 
        let point = NSPoint(x: 10, y: y)                        // the point, in the coordinates of the scrollView
        let hitPoint = scrollView.convert(point, to: tableView) // the same point, in the coordinates of the tableView

        // The row that lies that the hitPoint
        let row = tableView.row(at: hitPoint)

        // If there is a row there
        if row > -1 {
            let rect = tableView.rect(ofRow: row)                     // the rect that contains row's view
            let rowRect = tableView.convert(rect, to: scrollView)     // the same rect, in the scrollView's coordinates system
            let visibleRect = rowRect.intersection(scrollView.bounds) // the part of the row that visible from the scrollView
            let visibility = visibleRect.height / rowRect.height      // the percentage of the row that is visible

            for column in 0..<tableView.numberOfColumns {
                // Now iterate through every column in the row to change their color
                if let cellView = tableView.view(atColumn: column, row: row, makeIfNecessary: true) as? NSTableCellView {
                    let color = cellView.textField?.textColor

                    // The rows in a typical text-only tableView is 17px tall
                    // It's hard to spot their grayness so we exaggerate the
                    // alpha component a bit here:
                    let alpha = visibility == 1 ? 1 : visibility / 3

                    cellView.textField?.textColor = color?.withAlphaComponent(alpha)
                }
            }
        }
    }
}

Result:

在此处输入图片说明

I just solved the issue by myself.

Just set the contents view postsBoundsChangedNotifications to true and added an observer to NotificationCenter for NSViewBoundsDidChange . Works like a charm!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM