'Content Nodes' binding for NSTreeController

Originator:CharlesJS
Number:rdar://32385716 Date Originated:5-24-2017
Status:Open Resolved:
Product:macOS + SDK/AppKit Product Version:16F73
Classification:Enhancement Reproducible:N/A
 
Summary:

MOTIVATION

For the last decade, NSTreeController has been—let's admit it—a bit of a pain to use, mostly because of the way it handles tree nodes. Mainly:

- To manage a tree using an NSTreeController, one binds its 'Content Array' binding in Interface Builder. The NSTreeController can then be used to populate an NSOutlineView or another NSTreeController-aware view.

- However, in managing the tree, NSTreeController effectively duplicates the entire tree using a private subclass of NSTreeNode called NSTreeControllerTreeNode. Every node in the original tree is mirrored by a NSTreeControllerTreeNode in the parallel tree. This can confer a few advantages; the NSTreeControllerTreeNodes can be arbitrarily reordered by the NSTreeController without altering the order of nodes in the original tree, and individual objects can be duplicated in the original tree without causing issues, as each occurrence of a single object will get a unique NSTreeControllerTreeNode in the parallel tree. Finally, since NSTreeControllerTreeNodes are lazily created, structures that infinitely loop back on themselves are possible without breaking things.

- However, this approach has many drawbacks. For one, maintaining two parallel trees to represent the same data is, in many cases, a quite unnecessarily messy and awkward design, since many trees do not contain non-unique members. Furthermore, while it is easy to get the real model object from an NSTreeNode through its representedObject property, there is no way to go the other way and get an NSTreeNode corresponding to any given model object. This means that if the program wants to do something like select a given model object, or expand a given model object in an outline view, it will need to manually walk through the entire tree, checking the representedObject property on each node until it finds the desired node. This can be prohibitively expensive for large trees, resulting in an inferior user experience as the user may have to contend with beachballs and stuttering, as well as diminished battery life.

PROPOSED SOLUTION:

For the common case where: 1) every node in a tree can be guaranteed to be at a unique object address, and 2) the program code is not making assumptions about the ordering of tree nodes, so that it does not matter if they are arbitrarily resorted, I propose the following:

1. Either make NSTreeControllerTreeNode public, or fold its functionality into the already-public NSTreeNode.

2. Introduce a new "Content Nodes" binding on NSTreeController. This binding will point to an array of objects which are required to be a subclass of NSTreeControllerTreeNode (or NSTreeNode). The NSTreeController will then use these nodes directly, instead of creating a whole separate tree of proxy nodes.

(to be continued in Steps To Reproduce due to the character limit)

Steps to Reproduce:
3. In the case where the "Content Nodes" binding is used instead of "Content Objects", the childrenKeyPath, countKeyPath, and leafKeyPath key paths will be ignored, and it will no longer be an error to leave these blank in Interface Builder. The appropriate properties on NSTreeNode will be used instead of these key paths.

4. The "Content Objects" binding will continue to be available for cases in which features like non-uniqueness are required.

This will confer the following advantages:

- A cleaner model, without all the parallelism that is seen currently.

- Better performance for applications that need to select particular model items, or use NSOutlineView methods such as -expandItem:expandChildren:, since there will no longer be any need to traverse the whole tree to find the needed node.

- A much easier experience for the user, which parallels more closely the behavior of NSArrayController.

Expected Results:
A way to build the NSTreeNode tree by oneself, instead of having to duplicate one's existing tree, by way of a new 'Arranged Nodes' binding on NSTreeController.

Observed Results:
NSTreeController and its NSTreeControllerTreeNodes are currently messy, awkward, and ill-performing.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!