Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why might an NSTableView redraw every cell on scroll?

I have an NSTableView with 5 columns, each containing a stock NSTableCellView in the nib. (The stock cells have a text box and an optional image.) When populated, the table has around 50 rows. Everything displays fine, but scrolling performance is pretty bad. It looks like this is happening because every cell gets a drawRect: message for its full rect whenever the table scrolls. However, neither reloadData nor reloadDataForRowIndexes:ColumnIndexes: is getting called, so it's not that. It's not the contents of the cells, either: I tried commenting out all my code to just leave the default cell image and text for each cell, and performance is the same. While scrolling, none of the cells get updated. (I put a breakpoint in tableView:viewForTableColumn:row: to make sure.)

My implementation has the following delegate methods:

  • tableView:viewForTableColumn:row: in the delegate; this creates and populates new cells via makeViewWithIdentifier:owner:
  • numberOfRowsInTableView: in the data source; this returns a constant number
  • tableView:sortDescriptorsDidChange: in the data source

That's it! Not very complicated, and yet.

I feel like I'm missing something completely obvious. What could be causing these redraws?


EDIT: Come to think of it, several other applications (uTorrent, Xcode) seem to exhibit the same slow scrolling behavior. You can really see it if you look at CPU usage while scrolling. On the other hand, Activity Monitor has buttery-smooth scrolling that barely spikes the CPU at all. How do I get that in my app?


EDIT 2: I think I found my mistake. According to Apple:

In iOS apps, Core Animation is always enabled and every view is backed by a layer. In OS X, apps must explicitly enable Core Animation support by doing the following:

  • Link against the QuartzCore framework. (iOS apps must link against this framework only if they use Core Animation interfaces explicitly.)
  • Enable layer support for one or more of your NSView objects by doing one of the following:

    • In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for the selected view and its subviews. It is recommended that you enable layer support in the content view of your window whenever possible.
    • For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the view should use layers.

Enabling layer support in one of the preceding ways creates a layer-backed view. With a layer-backed view, the system takes responsibility for creating the underlying layer object and for keeping that layer updated. In OS X, it is also possible to create a layer-hosting view, whereby your app actually creates and manages the underlying layer object. (You cannot create layer-hosting views in iOS.) For more information on how to create a layer-hosting view, see “Layer Hosting Lets You Change the Layer Object in OS X.”

I'll add an answer as soon as I fix my performance issues. With a cursory pass, my scrolling is still bumpy, but my CPU usage has dropped from 70% to 10% while scrolling.

like image 317
Archagon Avatar asked Nov 13 '13 19:11

Archagon


1 Answers

For the record... Edit 2 by the OP makes the world of difference.

In iOS apps, Core Animation is always enabled and every view is backed by a layer. In OS X, apps must explicitly enable Core Animation support by doing the following:

Link against the QuartzCore framework. (iOS apps must link against this framework only if they use Core Animation interfaces explicitly.) Enable layer support for one or more of your NSView objects by doing one of the following:

In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for the selected view and its subviews. It is recommended that you enable layer support in the content view of your window whenever possible. For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the view should use layers. Enabling layer support in one of the preceding ways creates a layer-backed view. With a layer-backed view, the system takes responsibility for creating the underlying layer object and for keeping that layer updated. In OS X, it is also possible to create a layer-hosting view, whereby your app actually creates and manages the underlying layer object. (You cannot create layer-hosting views in iOS.) For more information on how to create a layer-hosting view, see “Layer Hosting Lets You Change the Layer Object in OS X.”

like image 73
Michael Avatar answered Nov 24 '22 11:11

Michael