NSOutlineView with Diffable Data Source

Hi, On macOS, there seems to be a NSTableViewDiffableDataSource and an NSCollectionViewDiffableDataSource, but there seems to be nothing for NSOutlineView. Is it possible to somehow use NSTableViewDiffableDataSource with NSOutlineView that I'm missing?

If not, is it possible to use NSTableView with NSTableViewDiffableDataSource to show a 'table' with section headers?

In theory, anything's possible.

There's a GitHub project trying to implement an "OutlineViewDiffableDataSource" but it hasn't been touched for years.

I've also seen various attempts to reimplement outline views or to implement them on iOS prior to diffable data sources. So that can be done too.

What are you hoping to get that's better than the current NSTableView/NSOutlineView? It's certainly difficult to use, but then so are diffable data sources. My outline views on iOS with undo/redo support were much more difficult to achieve than any equivalent on macOS.

First of all, don't use the comments in the forums. They effectively hide your reply. When searching for new or updated questions, your question still shows up with only one reply. Your comment isn't listed.

If you already have it working on macOS with an outline view, I strongly recommend you just resolve those few remaining issues. Trying to use a diffable data source is the path of greatest resistance. Even on iOS, the only reason to use it is because there's no other option. It's much more difficult. It would require a significant rearchiteture effort.

On iOS, what you see as an outline view is just a collection view where the cells are simply full-width. That hierarchical display that makes it look like an outline view is implemented with both NSDiffableDataSourceSnapshot and NSDiffableDataSourceSectionSnapshot classes.

It's really tricky. For example, because it's a collection view, there are no rows. You can only insert before another identifier, after another identifier, or append to a parent. The entire concept of a hierarchical list was something that was clearly thrown in at the last minute, without much documentation.

Thanks for the feedback. I'm just concerned with the macOS implementation of NSOutlineView. The full story is that I've been using this in my app's sidebar for a number of years, and it's been solid, but starting with macOS15, I've started getting dozens of crashes related to the NSOutlineView implementation, with many different types of crash report signatures, which all seem internal to Swift and/or NSOutlineView. The errors aren't reproducible on my side, but I have received over a 1000 reports in total already, all from devices macOS15, with no change on my side. I made a thread about this here and filed a feedback, but haven't received any response: https://developer.apple.com/forums/thread/767447

I basically need the sidebar to show what on iOS would be done with a sectioned UITableView (which doesn't exist on macOS). It shows a section with some fixed 'filters', a section with a list of 'tags' and more sections with list of 'groups', each with a section group header, and each row showing a name and a 'count'. I use a dictionary to drive the NSOutlineView. It loads fine, but if something changes, I have to update the 'count' of the item (by updating the count property of the item in the dictionary) and call outlineView.reloadItem, which ends up causing the crash in 1% of the cases.

So I am considering these options ...

  1. keep digging deeper, maybe do something different to find the cause of the crash or to avoid it altogether. But I've been looking at this issue for a few weeks, I can't reproduce the issue on my own, and it's tough to do trial-and-error with a production issue
  2. replace the sidebar with diffable data source ... I have a similar implementation on iOS so it would improve consistency as well. But it doesn't seem to be possible with NSOutlineView, only NSTableView (or collection view)
  3. replace it with a SwiftUI view. This might be most amount of work, since I would have implement things like reordering and drag-and-drop which the current implementation supports

That's the core of it. Any thoughts / feedback would be welcome.

I've started getting dozens of crashes related to the NSOutlineView implementation, with many different types of crash report signatures, which all seem internal to Swift and/or NSOutlineView.

Thanks for the heads up. I haven't deployed my code at all yet. I think I've seen some comments that Apple is replacing lower-level Objective-C code with Swift. Your crash logs look like that.

I made a thread about this here and filed a feedback, but haven't received any response: https://developer.apple.com/forums/thread/767447

Not surprising.

I basically need the sidebar to show what on iOS would be done with a sectioned UITableView (which doesn't exist on macOS).

You should be able to do that easily enough. macOS uses header rows instead of sections. But you can float and style them and they'll look nice. As far as that goes, if you want to be clever with styling, you could probably draw the row backgrounds so that it looks identical to iOS.

I use a dictionary to drive the NSOutlineView.

That sounds very bad. Table and outline views should be driven from an array. In outline views, the root is an array. Each element has an array of children.

It loads fine, but if something changes, I have to update the 'count' of the item (by updating the count property of the item in the dictionary) and call outlineView.reloadItem, which ends up causing the crash in 1% of the cases.

Both of your crashes involve parent/child relationships. I saw another post recently here in the forum that suggested dictionaries on Swift aren't entirely stable. It sounds like your data model is changing underneath the UI.

In my opinion, the only time you would ever call reloadItem is to update an item. Never use it to update children. Update children in some other fashion where it can be animated. I think this may be the problem. Table and outline views are really old. They still support old usage methods that don't require animation. That may be hiding bugs that you would have seen if you had done the extra work to make sure everything is animated. That can be a whole lot of extra work and could even require a change of architecture.

I have a similar implementation on iOS so it would improve consistency as well. But it doesn't seem to be possible with NSOutlineView, only NSTableView (or collection view)

When I did that on iOS, it seemed like the hierarchical representation was the flakiest part of it. I seem to have gotten it to work, but I don't have much confidence in it. You can certainly try it if you want to, but it would still be a Mac implementation. You've already seen how much anyone cares about Mac issues.

replace it with a SwiftUI view

I started my project in SwiftUI. But after a certain level of hacks, I realized that it just wasn't going to work. When considering the amount of work this will take, also consider the amount of work it will take to reimplement it later without SwiftUI. Plus, these kinds of Split View displays have changed significantly in SwiftUI and could change again.

It sounds like trying to use a dictionary as a data source is the problem here. NSOutlineView is more primitive than UICollectionView. If an item isn't expanded, then you don't need to update the UI for changes to children. But if an item is expanded, then you'll need to manually add rows to accommodate what has already been changed in the data model.

The way I did it, I have a "data source" wrapper around the various diffable data source snapshots on iOS. I have a similar "data source" wrapper on the Mac side with a very similar interface. But the Mac "data source" actually does some UI manipulation just due to the ancient, kludgy nature of outline views. From from a higher level, view controller perspective, both platforms are virtually identical.

That's really the way it works anyway. On iOS, you have your data model and then the diffable data source is just an opaque structure of identifiers that has to match the data source. On the Mac, the outline view itself plays that role in maintaining a structure for the underlying items.

Just following up on this: I managed to fix the issue! As I mentioned, the problem was mostly with updating the outlineView as data changed. I was trying to reload the 'item' when possible, and manually inserting / deleting rows in different sections as the data changed. I was doing this to keep the changes animated, and also to preserve selection. Now I'm just calling 'reloadData' instead... to preserve selection, I save the selected item(s) first, and manually select them after reloadData() is done. Animation wasn't important. Now it solved all the crash problems I had.

NSOutlineView with Diffable Data Source
 
 
Q