I have to add a source list (à la iTunes) to my Mac application. For that, I try the (SidebarDemo from Apple). It works fine. I added a button in this demo to demonstrate the effect of the reloadData
method on NSOutlineView
. And as you can see in the image there is a problem (on the left, before the reloadData
call, and on the right, after the reloadData
call). The badges disappear, icons change, etc.
What is the problem? Should I avoid using reloadData
on NSOutlineView
?
I'm using OS X 10.8.2, SDK 10.8 and Xcode 4.5.2.
You can download the modified SidebareDemo project here.
Thank you!
The SidebarDemo sample code makes the mistake of using the same object to represent multiple rows in the outline view. In particular, the underlying data used by the data source is created like so:
_childrenDictionary = [NSMutableDictionary new];
[_childrenDictionary setObject:[NSArray arrayWithObjects:@"ContentView1", @"ContentView2", @"ContentView3", nil] forKey:@"Favorites"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:@"ContentView1", @"ContentView2", @"ContentView3", nil] forKey:@"Content Views"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:@"ContentView2", nil] forKey:@"Mailboxes"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:@"ContentView1", @"ContentView1", @"ContentView1", @"ContentView1", @"ContentView2", nil] forKey:@"A Fourth Group"];
NSString literals of identical value are uniqued by the compiler, so each occurrence of @"ContentView1"
refers to the same object in memory. The result of this is that when the code inside -outlineView:viewForTableColumn:item:
looks up the parent of the item to determine which icon or unread state to use, -[NSOutlineView parentForItem:]
will only ever return a single parent for all of the @"ContentView1"
items. The fact that it works at all in the initial case appears to be an accident in implementation. The order in which the calls to -outlineView:viewForTableColumn:item:
are made is slightly different during the initial load vs the reload.
The solution would be to use unique objects to represent each item in the outline view. The most trivial modification to the SidebarDemo sample that achieves this is to create a mutable copy of each NSString value before storing it in _childrenDictionary
:
_childrenDictionary = [NSMutableDictionary new];
[_childrenDictionary setObject:[NSArray arrayWithObjects:[@"ContentView1" mutableCopy], [@"ContentView2" mutableCopy], [@"ContentView3" mutableCopy], nil] forKey:@"Favorites"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:[@"ContentView1" mutableCopy], [@"ContentView2" mutableCopy], [@"ContentView3" mutableCopy], nil] forKey:@"Content Views"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:[@"ContentView2" mutableCopy], nil] forKey:@"Mailboxes"];
[_childrenDictionary setObject:[NSArray arrayWithObjects:[@"ContentView1" mutableCopy], [@"ContentView1" mutableCopy], [@"ContentView1" mutableCopy], [@"ContentView1" mutableCopy], [@"ContentView2" mutableCopy], nil] forKey:@"A Fourth Group"];
In real-world code you're unlikely to be bitten by this problem since your underlying data objects would be composed of instances of your model classes,, rather than consisting only of string literals.