Exploring this further, I tried switching my relationships to be ordered. Since NSTreeController apparently can't use an ordered relationship directly, I added a computed property called rulesArray and told my NSTreeController to use that as the child key path. I also added a little code as above to see when observers were added. Finally, I added some code for the various keyPathsForValuesAffecting methods to try and cause the NSTreeController to also observe the real property.
I noticed the keyPathsForValuesAffecting methods were never called. From the example implementation in the KVO guide, I noticed the default case calls to the same method on super, so I tried adding a super.addObserver(...) at the end of the override, and it seems to be working!
The extensions to my Core Data classes look like this:
extension Layer {
@objc public override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
print("Layer.keyPathsForValuesAffectingValue(forKey \(key)) called")
switch key {
case "rulesArray" :
return Set(["rules"])
default :
return super.keyPathsForValuesAffectingValue(forKey: key)
}
}
@objc public dynamic var rulesArray: [PolicyItem] {
return self.rules?.array as? [PolicyItem] ?? []
}
public override func addObserver(
_ observer: NSObject,
forKeyPath keyPath: String,
options: NSKeyValueObservingOptions = [],
context: UnsafeMutableRawPointer?)
{
print("Adding observer \(String(describing: observer)) to object \(String(describing: self)).\(keyPath)")
super.addObserver(observer, forKeyPath: keyPath, options: options, context: context)
}
}
Each object is extended in a way that the rulesArray comes from the real relationship (or in the case of case of a Rule, comes from the Rule's passToLayer's real relationship, if it exists).
And now, when the ordered set is rearranged, the UI updates as expected! I don't actually need the addObserver(...) override, so I'm going to remove it. Still, thought this would be useful to anybody else who has a similar problem.