Filtering a NSTreeController / NSOutlineView

Hi,

I have a NSOutlineView populated by a NSTreeController and I'd like to filter it with a NSSearchField :

According to this post, it is not possible directly because NSTreeController doesn't have a filter predicate but according to that answer, we could filter the nodes of the array which is the content of the NSTreeController.

I tried this method but I don't have the good result :

Some nodes which does not contain the string still stay in the NSOutlineView.

Like I said in this post, I'm not familiar with the predicates so maybe my error comes from that. Here it is :

let thePredicate = NSPredicate(format: "%K CONTAINS[cd] %@", "name", theStr)

Also, the difficulty here is I need to keep the parent node even if it doesn't have the predicate condition if one of its child has it.

Other second solution is to put the result in a NSTableView but I need to build the same cell than in the NSOutlineView, etc...

Any idea ?

Thx.

  • Can you show enough code to reproduce the issue?

  • It is in the answer I spoke about.

    Thx for your help.

  • Seems you do not like to make your question more appealing to readers.

Add a Comment

Replies

I simply took this code :

class PredicateOutlineNode: NSObject {

    typealias Element = PredicateOutlineNode

    @objc dynamic var children: [Element] = [] {
        didSet {
            propagatePredicatesAndRefilterChildren()
        }
    }

    @objc private(set) dynamic var filteredChildren: [Element] = [] {
        didSet {
            count = filteredChildren.count
            isLeaf = filteredChildren.isEmpty
        }
    }

    @objc private(set) dynamic var count: Int = 0
    @objc private(set) dynamic var isLeaf: Bool = true

    var predicate: NSPredicate? {
        didSet {
            propagatePredicatesAndRefilterChildren()
        }
    }

    private func propagatePredicatesAndRefilterChildren() {
        // Propagate the predicate down the child nodes in case either
        // the predicate or the children array changed.
        children.forEach { $0.predicate = predicate }

        // Determine the matching leaf nodes.
        let newChildren: [Element]

        if let predicate = predicate {
            newChildren = children.compactMap { child -> Element? in
                if child.isLeaf, !predicate.evaluate(with: child) {
                    return nil
                }
                return child
            }
        } else {
            newChildren = children
        }

        // Only actually update the children if the count varies.
        if newChildren.count != filteredChildren.count {
            filteredChildren = newChildren
        }
    }
}

Thx.