The NSPredicateEditor is an excellent control that can be used to quickly give your users the ability to build up an NSPredicate
. I recently added one to a Mac application I was developing, and was surprised to see that, by default, Xcode4 only supports filtering by date, not by time.
For my application, I needed a much more accurate filtering capability, so I started to dig around to see what I could find. I found an excellent starting blog post followed up by another more advanced post that gave me some really good background information.
These two blog posts also sowed the seed that inspired this post.
Back To Coding
As far as I know, you cannot add time support to your NSPredicateEditor
rows using Interface Builder in Xcode4. You need to drop back into code… come on in, the water is fine
As outlined in the previously referenced posts, each NSPredicateEditorRowTemplate
is responsible for providing the individual controls by overriding the templateView
method. As you can see from the above screenshot, this method normally returns an array containing the following controls: NSPopupButton
, NSPopupButton
and NSDatePicker
respectively.
In order to facilitate time support, we are going to create a subclass of NSPredicateEditorRowTemplate
and modify the third element to change the date components that are displayed.
Creating our subclass
#import <Cocoa/Cocoa.h> @interface BDTimestampRowTemplate : NSPredicateEditorRowTemplate -(id)initWithLeftExpressions:(NSArray *)leftExpressions; @end
#import "BDTimestampRowTemplate.h" @implementation BDTimestampRowTemplate -(id)initWithLeftExpressions:(NSArray *)leftExpressions { NSAttributeType rightType = NSDateAttributeType; NSComparisonPredicateModifier modifier = NSDirectPredicateModifier; NSArray *operators = [NSArray arrayWithObjects: [NSNumber numberWithUnsignedInteger:NSLessThanPredicateOperatorType], [NSNumber numberWithUnsignedInteger:NSLessThanOrEqualToPredicateOperatorType], [NSNumber numberWithUnsignedInteger:NSGreaterThanPredicateOperatorType], [NSNumber numberWithUnsignedInteger:NSGreaterThanOrEqualToPredicateOperatorType], [NSNumber numberWithUnsignedInteger:NSEqualToPredicateOperatorType], [NSNumber numberWithUnsignedInteger:NSNotEqualToPredicateOperatorType], nil]; NSUInteger options = 0; return [super initWithLeftExpressions:leftExpressions rightExpressionAttributeType:rightType modifier:modifier operators:operators options:options]; } -(NSArray *)templateViews { // get the list of views in the template NSArray *views = [super templateViews]; // get out the date picker control NSDatePicker *datePicker = [views objectAtIndex:2]; // change the flags so that it displays date AND time NSDatePickerElementFlags flags = NSYearMonthDayDatePickerElementFlag | NSHourMinuteSecondDatePickerElementFlag; // apply the flags [datePicker setDatePickerElements:flags]; return views; } @end
Note that I have hard-coded (in this class) the supported operators. This is because a timestamp field really can only meaningfully support this subset. It makes the initWithLeftExpressions
method just a little cleaner.
Using the techniques described in Dave’s excellent blog post, you can then use this new class to modify your timestamp row template.
It will result in a template row that looks like:
You will notice that it still doesn’t show seconds. Through some trial and error, I have found that the time-only flag will use seconds, but if you use date and time flags, you can’t get seconds.