BlackDog Foundry Bookmark This page

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.

Default Look and Feel

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:

Modified predicate row

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.

2 Comments »

  1. Bill says:

    Great article. I used IB to create the NSPredicateEditor, and I created a custom NSPredicateEditorRowTemplate class for any NSPredicateEditorRowTemplate that contained a date. The custom class overrides one method: templateViews — see below.

    @implementation TSPredicateEditorRowTemplate

    -(NSArray *)templateViews { NSArray *views = [super templateViews]; for( id view in views ) { if( [view isKindOfClass:[NSDatePicker class]] ) { if( [view tag] == 0 ) { // only need to do this once, so set a flag. NSDatePickerElementFlags flags = NSYearMonthDayDatePickerElementFlag | NSHourMinuteSecondDatePickerElementFlag; [view setDatePickerElements:flags];

                NSRect frame = [view frame];
                frame.size.width += 45; // make the cell wider for the time controls
                [view setFrame:frame];
                [view setTag:1];
            }
        }
    }
    return views;
    

    }

  2. Carter says:

    Actualy the easiest way to size the NSDatePicker properly is to call it’s “sizeToFit” method.

Leave a Comment »




Categories

Copyright © 2012 BlackDog Foundry