Why is my NSRuleEditor not displaying criteria?

72 views Asked by At

I am trying to get an NSRuleEditor to display criteria. It works (creates a static label) if I give it a single criterion, but the moment I give it more than one, each row of the rule editor is blank (no subviews are added other than the add/remove buttons). Why would this be? My code is as follows.

struct RuleItem {

    var text: String

    var children: [RuleItem] = []

}

var ruleItems = [
    RuleItem(text: "hello"),
    RuleItem(text: "world", children: [
        RuleItem(text: "child 1"),
        RuleItem(text: "child 2")
    ])
]

// In class conforming to NSRuleEditorDelegate:

func ruleEditor(_ editor: NSRuleEditor, numberOfChildrenForCriterion criterion: Any?, with rowType: NSRuleEditor.RowType) -> Int {
    if let item = criterion as AnyObject as? RuleItem {
        return item.children.count
    } else {
        return ruleItems.count
    }
}

func ruleEditor(_ editor: NSRuleEditor, child index: Int, forCriterion criterion: Any?, with rowType: NSRuleEditor.RowType) -> Any {
    if let item = criterion as AnyObject as? RuleItem {
        return item.children[index]
    } else {
        return ruleItems[index]
    }
}

func ruleEditor(_ editor: NSRuleEditor, displayValueForCriterion criterion: Any, inRow row: Int) -> Any {
    (criterion as AnyObject as! RuleItem).text as NSString
}
1

There are 1 answers

0
ThatsJustCheesy On

I solved this myself (after a few frustrating hours).

The problem is that we are returning instances of a Swift structure from the Objective-C protocol method -ruleEditor:child:forCriterion:withRowType. These are automatically converted to instances of __SwiftValue (an Objective-C class) by the compiler. This seems well and good, until we read the following at the top of NSRuleEditor.h:

Each node in the tree is represented by a "criterion," which can be any object. As the user chooses from the popup menus, the rule editor view will query its delegate for the child criteria of the chosen criterion. The only restriction on the criteria is that two criterions should be considered equal by isEqual: if they represent the same node, and must not be equal if they represent different nodes.

So our problem seems to be that the __SwiftValues we're returning don't have a meaningful definition of isEqual:. Everything works as expected if we use Objective-C-compatible classes instead of Swift structures.

Code that works:

class RuleItem: NSObject {
    
    init(text: String, children: [RuleItem] = []) {
        self.text = text
        self.children = children
    }
    
    var text: String
    
    var children: [RuleItem] = []
    
}

var ruleItems = [
    RuleItem(text: "hello"),
    RuleItem(text: "world", children: [
        RuleItem(text: "child 1"),
        RuleItem(text: "child 2")
    ])
]

// In class conforming to NSRuleEditorDelegate:

func ruleEditor(_ editor: NSRuleEditor, numberOfChildrenForCriterion criterion: Any?, with rowType: NSRuleEditor.RowType) -> Int {
    if criterion == nil {
        return ruleItems.count
    }
    return (criterion as! RuleItem).children.count
}

func ruleEditor(_ editor: NSRuleEditor, child index: Int, forCriterion criterion: Any?, with rowType: NSRuleEditor.RowType) -> Any {
    if criterion == nil {
        return ruleItems[index]
    }
    return (criterion as! RuleItem).children[index]
}

func ruleEditor(_ editor: NSRuleEditor, displayValueForCriterion criterion: Any, inRow row: Int) -> Any {
    (criterion as! RuleItem).text
}

By the way, NSRuleEditor.h is leagues more helpful than the actual documentation pages; do yourself a favor and read it through.