cocoanspredicatenspredicateeditornsexpression

NSPredicateEditorRowTemplate: how to to populate right side popup


I am trying to generate an NSPredicateEditorRowTemplate that on the left side has a number of property names from an entity Foo, among which is the property bar. When the user selects 'bar', the right side should become popup which contains all values of 'bar'.

How can I best populate the right side popup? All unique values of bar are stored in an NSMutableArray, so perhaps I can use KVO to change the row template when the array changes.

Is there a way that I can use code to easily change the values in a right side popup in an NSPredicateEditor row? I can enter a few static values in IB, but that will not do in this situation.

EDIT

Having read a good deal of related Q&A's, including NSPredicateEditor in Xcode 4 and the excellent answer to it by @Dave DeLong, I think a good bit of the work can be done like this:

NSArray *leftexp = @[[NSExpression expressionForKeyPath:@"name"],[NSExpression expressionForKeyPath:@"married"]];
NSArray *rightexp = @[[NSExpression expressionWithFormat:@"One"],[NSExpression expressionWithFormat:@"Two"]];
NSPredicateEditorRowTemplate *template = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:leftexp rightExpressions:rightexp modifier:NSDirectPredicateModifier operators:@[@(NSEqualToPredicateOperatorType)] options:0];

NSPredicateEditorRowTemplate *compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:@[@(NSOrPredicateType)]];

[self.predicateEditor setRowTemplates:@[template,compound]];
NSEntityDescription *description = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.managedObjectContext];
self.predicate = [NSPredicate predicateWithFormat:@"name == ''"];

I've seen a few ways to make the basic NSPredicateEditor appear (with at least the compound line), but it seems to me that there has to be an elegant way to do that, the way it was meant to be done. Can't find that though, can anyone help me along with this?


Solution

  • Some would probably argue that the way it is meant to be done is in Interface Builder. That would probably be fine if you're familiar with how row templates work, how they get combined, etc. Personally, I prefer to create the programmatically because I can be explicit about how the row templates function.

    Let's go through an example of how this all works together, and then you can decide which approach works best for you:

    With this in mind, here's how you'd create that programmatically:

    // these defines are for brevity on the web page; please don't actually do this
    #define NSPERT NSPredicateEditorRowTemplate
    #define KP(_kp) [NSExpression expressionForKeyPath:(_kp)]
    #define CV(_cv) [NSExpression expressionForConstantValue:(_cv)]
    
    NSPERT *compound = [[NSPERT alloc] initWithCompoundTypes:@[@(NSAndPredicateType), 
                                                               @(NSOrPredicateType), 
                                                               @(NSNotPredicateType)]];
    
    NSPERT *bar = [[NSPERT alloc] initWithLeftExpressions:@[KP(@"bar")] 
                                         rightExpressions:@[CV(@"Bar1"), CV(@"Bar2"), CV(@"Bar3")] 
                                                 modifier:NSDirectPredicateModifier 
                                                operators:@[@(NSEqualToPredicateOperatorType), 
                                                            @(NSNotEqualToPredicateOperatorType)] 
                                                  options:0];
    
    NSPERT *baz = [[NSPERT alloc] initWithLeftExpressions:@[KP(@"baz")] 
                             rightExpressionAttributeType:NSStringAttributeType 
                                                 modifier:NSDirectPredicateModifier 
                                                operators:@[@(NSEqualToPredicateOperatorType),
                                                            @(NSNotEqualToPredicateOperatorType)]
                                                  options:0];
    
    NSArray *templates = @[compound, bar, baz];
    [self.myPredicateEditor setRowTemplates:templates];
    

    Granted, this is a lot of typing just to create row templates. I understand that. I just think that it's also very explicit and easy to debug. That being said, you can also configure this exact same thing in Interface Builder. To do so, put an NSPredicateEditor in your xib, and give it 3 row templates. The Compound row template you'd leave alone, but the other two you'd change to be configured like this:

    For the "bar" template:

    "Bar" row template configuration

    For the "baz" template:

    "Baz" row template configuration

    If you compare these screenshots to the code above, it should (hopefully) be easy to see how one maps to the other. With the row templates configured like this, IB also shows the predicate editor in your window:

    Window with predicate editor

    If you want to do more advanced things, such as creating custom row templates, or populating the list of allowed constant values with something from your data model, then you'll have to drop to doing the configuration in code.


    Edit to answer comment question

    The modifier bit of these selectors corresponds to the NSComparisonPredicateModifer for the created NSComparisonPredicate. There are three modifiers: Direct, Any, and All.

    @"bar = 'Bar1'";
      ^ No modifier means this is NSDirectPredicateModifier
    
    @"ANY user.userName = 'markjs'";
      ^~~ use of "ANY" keyword means this is NSAnyPredicateModifier
    
    @"ALL user.age < 42";
      ^~~ use of "ALL" keyword means this is NSAllPredicateModifier
    

    In these three examples, they're all comparison predicates (because they have a left side, an operator, and a right side), but the use of "ANY" or "ALL" affects how the left side is interpreted. If either is present, then the predicate is expecting the left-hand side to evaluate to a collection of possibilities.

    Using the "direct" option (or 0) basically means you're going to be doing a one-to-one comparison.