How to select items in NSOutlineView without NSTreeController?
-
11-09-2019 - |
Question
I'm using NSOutlineView
without NSTreeController
and have implemented my own datasource. What is the best way to select an item?
NSOutlineView
support already expandItem:
and collapseItem:
. And I'm missing a handy method like `selectItem:. How can I do it programatically ?
Thank you.
Solution
Remember to look in superclasses when you can't find something. In this case, one of the methods you need comes from NSTableView, which is NSOutlineView's immediate superclass.
The solution is to get the row index for the item using rowForItem:
, and if it isn't -1 (item not visible/not found), create an index set with it with [NSIndexSet indexSetWithIndex:]
and pass that index set to the selectRowIndexes:byExtendingSelection:
method.
OTHER TIPS
Here is how I finally ended up. Suggestions and corrections are always welcome.
@implementation NSOutlineView (Additions)
- (void)expandParentsOfItem:(id)item {
while (item != nil) {
id parent = [self parentForItem: item];
if (![self isExpandable: parent])
break;
if (![self isItemExpanded: parent])
[self expandItem: parent];
item = parent;
}
}
- (void)selectItem:(id)item {
NSInteger itemIndex = [self rowForItem:item];
if (itemIndex < 0) {
[self expandParentsOfItem: item];
itemIndex = [self rowForItem:item];
if (itemIndex < 0)
return;
}
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: itemIndex] byExtendingSelection: NO];
}
@end
No, there isn't a selectItem:
method, but there is an rowForItem:
method. If you combine that with Peter's advice about using selectRowIndexes:byExtendingSelection:
above, you should have all the information you need.
If you really wanted to have a method to select an item, which I would recommend calling setSelectedItem:
for consistency's sake, you could write something like this in a category on NSOutlineView
- (void)setSelectedItem:(id)item {
NSInteger itemIndex = [self rowForItem:item];
if (itemIndex < 0) {
// You need to decide what happens if the item doesn't exist
return;
}
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:itemIndex] byExtendingSelection:NO];
}
I have no idea if this code actually works; I just dashed it off to illustrate the concept.
Here is a code snippet I used to programmatically select an item in a PXSourceList.
sourceList is a regular PXSouceList object and I wanted to select the second item in the first group of the outline.
NSInteger itemRow = [sourceList rowForItem:[[(SourceListItem *)[sourceListItems objectAtIndex:0] children] objectAtIndex:1]]; [sourceList selectRowIndexes:[NSIndexSet indexSetWithIndex:itemRow] byExtendingSelection:YES];
If you don't know yet, PXSourceList is an excellent replacement for an NSOutlineView if you re looking for itunes/mail style outlines. Pick it up here: PxSourceList
This is an old question, but the situation is still the same. As there was a request for a swift version, here's my take. I didn't find the accepted answer to work for me as I think you should interact directly with your data source class rather than extending the NSOutlineView. Annoyingly, the outline won't find rows unless they are expanded which is why it's simpler to use your dataSource. In my case I found that you have to expand the parent's of your items in reverse order, so that you start at the top level and work your way towards the actual item you're trying to expand. I feel like that should be built in, but unless I missed something - it's not.
In this example FileItem is my data source collection item class. It contains a property "parent" which needs to be valid if it is part of the hierarchy displayed.
func selectItem(_ item: FileItem, byExtendingSelection: Bool = false) {
guard let outlineView = outlineView else { return }
var itemIndex: Int = outlineView.row(forItem: item)
if itemIndex < 0 {
var parent: FileItem? = item
var parents = [FileItem?]()
while parent != nil {
parents.append(parent)
parent = parent?.parent
}
let reversedTree = parents.compactMap({$0}).reversed()
for level in reversedTree {
outlineView.expandItem(level, expandChildren: false)
}
itemIndex = outlineView.row(forItem: item)
if itemIndex < 0 {
print("Didn't find", item)
return
}
}
print("Expanding row", itemIndex)
outlineView.selectRowIndexes(IndexSet(integer: itemIndex), byExtendingSelection: byExtendingSelection)
outlineView.scrollRowToVisible(itemIndex)
}