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.

Was it helpful?

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)
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top