NSTableView Variable Row Heights Broken on macOS Ventura 13.0
Variable row heights in your NSTableView
might be broken in your apps on macOS Ventura 13.0 – it’s fixed with the upcoming 13.1, but that’s only available as a beta at the moment.
When you replace table contents by calling aTableView.reloadData()
, this will ingest the new data as usual, but the old row heights won’t be forgotten. This can affect scrolling. The row height cache, it seems, isn’t properly invalidated or cleared.
NSTableView
andNSOutlineView
now automatically estimate row heights for view-based table views whose delegates implementtableView(_:heightOfRow:)
and provide variable row heights. This provides performance improvements for table views with large numbers of rows by reducing the frequency of the calls totableView(_:heightOfRow:)
.
(macOS 13 Release Notes)
In macOS Ventura
NSTableView
now calculates the row heights lazily:
- row heights are calculated for rows which are in or near the scrolling viewport
- for the rest of the rows,
NSTableView
estimates the height based on the row heights that it has already measured- as the table is scrolled, the table view requests more row heights as needed and replaces the estimates with real measurements
@SkipRousseau shared how to fix this in a Slack:
-
Your app can opt out of row height estimation.
Test this via launch arguments in Xcode by adding
-NSTableViewCanEstimateRowHeights NO
. That essentially overrides aUserDefaults
key.1To do this programmatically, call
UserDefaults.standard.set(false, forKey: "NSTableViewCanEstimateRowHeights")
early in the app’s launch routine, e.g. duringapplicationWillFinishLaunching
. -
Invalidate the cache for each row index, in code via
NSTableView.noteHeightOfRows(withIndexesChanged:)
. Skip’s sample to invalidate the cache for all rows is:// Right after reloadData, NSAnimationContext.beginGrouping() NSAnimationContext.current.duration = 0 let entireTableView: IndexSet = .init(0 ..< self.tableView.numberOfRows) self.tableView.noteHeightOfRows(withIndexesChanged: entireTableView) NSAnimationContext.endGrouping()
For now, I tend towards disabling NSTableViewCanEstimateRowHeights
by default to get the old behavior and to prevent the bug on Ventura, and optionally enable this for macOS 13.1 during the launch procedure. Opting-out of the new behavior sounds easier to maintain than invalidating row indexes on 13.0 in code.
-
UserDefaults
don’t update when you put this key–value-pair into yourInfo.plist
. Thanks Daniel Alm for reporting this! ↩