NSPredicateEditor Sample Code
Created By: Debasis Das (19-May-2015)
In this tutorial we will create a sample application to demonstrate the usage of NSPredicateEditor & NSPredicateEditorRowTemplate.
We will also create a custom row template by subclassing NSPredicateRowTemplate.
A NSPredicateRowTemplate normally has the following elements
- A popup for the left expression
- A popup for the comparison operator
- A field (date field, text field or a constant value popup etc) or the right expression.
The custom row template will have an extra button and a text message.
The custom NSPredicateEditorRowTemplate will look like the below screen shot
The sample application will have the below User Interface once we are done building the application
NSPredicateEditor Sample Code
// AppDelegate.h
// KSPredicateEditor
// Created by Debasis Das on 5/17/15.
// Copyright (c) 2015 Knowstack. All rights reserved.
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject
@end
// AppDelegate.m // KSPredicateEditor #import "AppDelegate.h" #import "KSDepartmentsRowTemplate.h" #define DEFAULT_PREDICATE @"firstName = 'John' " \ @"OR lastName = 'Doe' " \ @"OR dateOfBirth > CAST('01/01/1985', 'NSDate') " \ @"AND dateOfBirth <= CAST('01/01/1995', 'NSDate') " \ @"OR country = 'United States'" \ @"OR department = 'Finance'" @interface AppDelegate () @property (weak) IBOutlet NSWindow *window; @property (weak) IBOutlet NSPredicateEditor *predicateEditor; @property (nonatomic,strong) NSPredicate *predicate; @property (assign) IBOutlet NSTextView *queryTextField; @end @implementation AppDelegate - (id)init { self = [super init]; if (self != nil) { _predicate = [NSPredicate predicateWithFormat:DEFAULT_PREDICATE]; } return self; } -(IBAction)generateQuery:(id)sender { [self.queryTextField setFont:[NSFont fontWithName:@"Helvetica" size:18.0]]; [self.queryTextField setString:[self.predicateEditor.objectValue description]]; } -(IBAction)predicateEditorAction:(id)sender{ NSLog(@"%s",__func__); } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { //This is required for the Any, All and None option in the predicate editor NSPredicateEditorRowTemplate *compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes:@[@(NSAndPredicateType),@(NSOrPredicateType),@(NSNotPredicateType)]]; NSPredicateEditorRowTemplate *firstNameRowTemplate = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:[NSArray arrayWithObject:[NSExpression expressionForKeyPath:(@"firstName")]] rightExpressionAttributeType:NSStringAttributeType modifier:NSDirectPredicateModifier operators:@[@(NSEqualToPredicateOperatorType), @(NSNotEqualToPredicateOperatorType)] options:0]; NSPredicateEditorRowTemplate *lastNameRowTemplate = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:[NSArray arrayWithObject:[NSExpression expressionForKeyPath:(@"lastName")]] rightExpressionAttributeType:NSStringAttributeType modifier:NSDirectPredicateModifier operators:@[@(NSEqualToPredicateOperatorType), @(NSNotEqualToPredicateOperatorType)] options:0]; NSArray *comparisonOperators = @[@(NSEqualToPredicateOperatorType),@(NSGreaterThanOrEqualToPredicateOperatorType),@(NSLessThanOrEqualToPredicateOperatorType),@(NSGreaterThanPredicateOperatorType),@(NSLessThanPredicateOperatorType)]; NSPredicateEditorRowTemplate *dateOfBirthTemplate = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:[NSArray arrayWithObject:[NSExpression expressionForKeyPath:(@"dateOfBirth")]] rightExpressionAttributeType:NSDateAttributeType modifier:NSDirectPredicateModifier operators:comparisonOperators options:0]; NSArray *countryList = [NSArray arrayWithObjects:[NSExpression expressionForConstantValue:@"United States"],[NSExpression expressionForConstantValue:@"Mexico"],[NSExpression expressionForConstantValue:@"Brazil"],[NSExpression expressionForConstantValue:@"Canada"],nil]; NSArray *equalNotEqualOperators = [NSArray arrayWithObjects: [NSNumber numberWithUnsignedInteger:NSEqualToPredicateOperatorType], [NSNumber numberWithUnsignedInteger:NSNotEqualToPredicateOperatorType],nil]; NSPredicateEditorRowTemplate *countryTemplate = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions:[NSArray arrayWithObject:[NSExpression expressionForKeyPath:(@"country")]] rightExpressions:countryList modifier:NSDirectPredicateModifier operators:equalNotEqualOperators options:0]; NSArray * leftExpressions = [NSArray arrayWithObject:[NSExpression expressionForKeyPath:@"department"]]; KSDepartmentsRowTemplate * departmentCustomRowTemplate = [[KSDepartmentsRowTemplate alloc] initWithLeftExpressions:leftExpressions]; NSArray *templates = @[compound, firstNameRowTemplate,lastNameRowTemplate,dateOfBirthTemplate,countryTemplate,departmentCustomRowTemplate]; [self.predicateEditor setRowTemplates:templates]; [self.predicateEditor setObjectValue:_predicate]; NSLog(@"%@",self.predicateEditor.rowTemplates); /* //LEFT EXPRESSION The left expression can be created using any of the following options + (NSExpression *)expressionWithFormat:(NSString *)expressionFormat argumentArray:(NSArray *)arguments NS_AVAILABLE(10_6,4_0); + (NSExpression *)expressionWithFormat:(NSString *)expressionFormat, ... NS_AVAILABLE(10_6,4_0); + (NSExpression *)expressionWithFormat:(NSString *)expressionFormat arguments:(va_list)argList NS_AVAILABLE(10_6,4_0); + (NSExpression *)expressionForConstantValue:(id)obj; // Expression that returns a constant value + (NSExpression *)expressionForEvaluatedObject; // Expression that returns the object being evaluated + (NSExpression *)expressionForVariable:(NSString *)string; // Expression that pulls a value from the variable bindings dictionary + (NSExpression *)expressionForKeyPath:(NSString *)keyPath; // Expression that invokes valueForKeyPath with keyPath + (NSExpression *)expressionForFunction:(NSString *)name arguments:(NSArray *)parameters; // Expression that invokes one of the */ //For creating the RIGHT EXPRESSION ATTRIBUTE TYPE the following options can be used /* NSUndefinedAttributeType = 0, NSInteger16AttributeType = 100, NSInteger32AttributeType = 200, NSInteger64AttributeType = 300, NSDecimalAttributeType = 400, NSDoubleAttributeType = 500, NSFloatAttributeType = 600, NSStringAttributeType = 700, NSBooleanAttributeType = 800, NSDateAttributeType = 900, NSBinaryDataAttributeType = 1000, NSTransformableAttributeType NS_ENUM_AVAILABLE(10_5,3_0) = 1800, // If your attribute is of NSTransformableAttributeType, the attributeValueClassName must be set or attribute value class must implement NSCopying. NSObjectIDAttributeType NS_ENUM_AVAILABLE(10_6,3_0) = 2000 */ //MODIFIER /* NSDirectPredicateModifier = 0, // Do a direct comparison NSAllPredicateModifier, // ALL toMany.x = y NSAnyPredicateModifier // ANY toMany.x = y */ //NSPredicateEditor supports the following OPERATORS /* NSLessThanPredicateOperatorType = 0, // compare: returns NSOrderedAscending NSLessThanOrEqualToPredicateOperatorType, // compare: returns NSOrderedAscending || NSOrderedSame NSGreaterThanPredicateOperatorType, // compare: returns NSOrderedDescending NSGreaterThanOrEqualToPredicateOperatorType, // compare: returns NSOrderedDescending || NSOrderedSame NSEqualToPredicateOperatorType, // isEqual: returns true NSNotEqualToPredicateOperatorType, // isEqual: returns false NSMatchesPredicateOperatorType, NSLikePredicateOperatorType, NSBeginsWithPredicateOperatorType, NSEndsWithPredicateOperatorType, NSInPredicateOperatorType, // rhs contains lhs returns true NSCustomSelectorPredicateOperatorType, NSContainsPredicateOperatorType NS_ENUM_AVAILABLE(10_5, 3_0) = 99, // lhs contains rhs returns true NSBetweenPredicateOperatorType NS_ENUM_AVAILABLE(10_5, 3_0) */ } - (void)applicationWillTerminate:(NSNotification *)aNotification { // Insert code here to tear down your application } @end
// KSDepartmentsRowTemplate.h
// KSPredicateEditor
// Created by Debasis Das on 5/18/15.
// Copyright (c) 2015 Knowstack. All rights reserved.
#import <Cocoa/Cocoa.h>
@interface KSDepartmentsRowTemplate : NSPredicateEditorRowTemplate
{
}
- (id) initWithLeftExpressions:(NSArray *)leftExpressions;
@end
// KSDepartmentsRowTemplate.m
#import "KSDepartmentsRowTemplate.h"
@implementation KSDepartmentsRowTemplate
- (id) initWithLeftExpressions:(NSArray *)leftExpressions {
NSArray * operators = [NSArray arrayWithObjects:
[NSNumber numberWithUnsignedInteger:NSEqualToPredicateOperatorType],
[NSNumber numberWithUnsignedInteger:NSNotEqualToPredicateOperatorType],
nil];
NSArray *departmentList = [NSArray arrayWithObjects:
[NSExpression expressionForConstantValue:@"Human Resources"],
[NSExpression expressionForConstantValue:@"Finance"],
[NSExpression expressionForConstantValue:@"Information Technology"],
[NSExpression expressionForConstantValue:@"Sales"],
nil];
return [super initWithLeftExpressions:leftExpressions
rightExpressions:departmentList
modifier:NSDirectPredicateModifier
operators:operators
options:0];
}
- (NSArray *) templateViews {
NSMutableArray * views = [[super templateViews] mutableCopy];
[views addObject:[self validationView]];
return views;
}
-(NSView *)validationView{
NSView *view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 400, 22.0)];
NSButton *button = [[NSButton alloc] initWithFrame:NSMakeRect(0.0, 2.0, 70.0, 18.0)];
[button setTitle:@"Validate"];
[button setTarget:self];
[button setAction:@selector(validationAction:)];
[button setBezelStyle:NSRecessedBezelStyle];
NSTextField *textField = [[NSTextField alloc] initWithFrame:NSMakeRect(80.0, 2.0, 220.0, 18.0)];
[textField setBordered:NO];
[textField setStringValue:@"This is a custom message"];
[textField setDrawsBackground:NO];
[textField setTextColor:[NSColor redColor]];
[view addSubview:button];
[view addSubview:textField];
return view;
}
-(IBAction)validationAction:(id)sender
{
NSLog(@"%s",__func__);
}
@end
The source code can be downloaded from the following location
NSPredicateEditor Sample Code, Custom NSPredicateEditorRowTemplate Sample Code
Thanks for writing this article and providing the sample code.
I noticed you have a nested compound predicate. How do you add one of those with the GUI?
I’ve tried to do it, but it doesn’t work by pressing the [+] button next to the “Any of the following are true” option at the top.
Do you have to have a button outside of the NSPredicateEditor to be able to do that? I would have thought that the [+] beside the first item would allow you to add an additional compound section that you could then add fields underneath.
Hi Brendan,
Click + while pressing option key. That should allow you to add another compound predicate.
Yes I know its not intuitive enough.
Best Regards
Debasis