NSOutlineView row(forItem:) doesn't work when the view's items are Swift value types

Originator:brandon
Number:rdar://38734802 Date Originated:2018/03/21
Status:Closed, Duplicate Of 36113646 Resolved:
Product:AppKit Product Version:10.13
Classification:Bug Reproducible:Always
 
Area:
AppKit

Summary:

NSOutlineView has a very flexible data source protocol that in Swift 3+ allows providing items with type Any. This works well for me in most cases where I can provide data in a Swift value type like a struct and the outline view displays and selects items correctly.

The one case where this failed was when I needed to look up the row for one of those structs with row(forItem:), and it would always return -1 indicating the item wasn't found.

Making the struct conform to Equatable didn't work, but changing the struct to a class did. It seems that despite the NSOutlineView (and related protocol APIs) constraining item arguments to Any, reference equality is used internally for some operations, at least one of which is row(forItem:).

The automatic boxing of value types done starting in Swift 3 seems like a bit of a trap when paired with reference equality, because it _mostly_ works here.

I'm not sure what the best solution is, but three ideas in order of preference are:

- Constrain items to Equatable types and use that definition of equality
- Constrain items to AnyObject and continue to use reference equality
- Continue to allow Any items, and document that reference equality is used internally

I understand that the use of reference equality is probably rooted in NSOutlineView only ever having to work with objects. I also understand that any changes to this behaviour could have _lots_ of implications for existing code and that something like the last option might be all that could realistically be done.

Other reports I saw of this online:
- https://forums.developer.apple.com/thread/68477
- https://stackoverflow.com/questions/41056547/using-a-swift-struct-in-an-nsoutlineview

Steps to Reproduce:

I've attached a playground that demonstrates this. Changing the type parameter of the generic data source between StructItem and ClassItem will cause different results when row(forItem:) is invoked at the bottom.

(I'm not sure if the fact that I used a protocol that doesn't require Equatable conformance means the provided implementation could somehow be a reason for StructItem's == not being invoked, but if you manually replace the T type parameter with StructItem in `items` the same effect is seen.)

Expected Results:

An API that allows value types as arguments should have the same behaviour for both value and reference types.

Actual Results:

The row(forItem:) method doesn't work with value types.

Version/Build:

macOS 10.13 SDK

Configuration:

Xcode 9.2 (9C40b)

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!