DatePicker/ViewController.m
/* |
Copyright (C) 2018 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
This sample's main view controller. |
*/ |
#import "ViewController.h" |
@interface ViewController () <NSDatePickerCellDelegate> |
@property (assign) id localChangedObserver; |
@property (strong) NSDatePicker *datePickerControl; |
@property (weak) IBOutlet NSBox *outerBox; // Used to hold the NSDatePicker and time elements. |
@property (weak) IBOutlet NSView *datePickerBox; // Used to hold the NSDatePicker itself. |
@property (weak) IBOutlet NSButton *setToTodayButton; |
// Formatted date labels. |
@property (weak) IBOutlet NSTextField *dateResult1; |
@property (weak) IBOutlet NSTextField *dateResult2; |
@property (weak) IBOutlet NSTextField *dateResult3; |
@property (weak) IBOutlet NSTextField *dateResult4; |
@property (weak) IBOutlet NSTextField *dateResult5; |
// Appearance. |
@property (weak) IBOutlet NSPopUpButton* pickerStylePopup; |
// Date elements (3 checkboxes). |
@property (weak) IBOutlet NSButton *YearMonthElementFlag; |
@property (weak) IBOutlet NSButton *YearMonthDayElementFlag; |
// Time elements (3 checkboxes). |
@property (weak) IBOutlet NSButton *HourMinuteElementFlag; |
@property (weak) IBOutlet NSButton *HourMinuteSecondElementFlag; |
@property (weak) IBOutlet NSButton *overrideDateCheck; |
@property (weak) IBOutlet NSDatePicker *overrideDate; |
// Date range. |
@property (weak) IBOutlet NSButton *singleDateMode; |
@property (weak) IBOutlet NSButton *rangeDateMode; |
@property (weak) IBOutlet NSTextField *secondsRangeEdit; |
@property (weak) IBOutlet NSTextField *secondsRangeEditLabel; |
@property (weak) IBOutlet NSDatePicker *minDatePicker; |
@property (weak) IBOutlet NSDatePicker *maxDatePicker; |
@end |
#pragma mark - |
@implementation ViewController |
// ------------------------------------------------------------------------------- |
// viewDidLoad |
// ------------------------------------------------------------------------------- |
- (void)viewDidLoad |
{ |
[super viewDidLoad]; |
// Based our date formatter on CFDateFormatter: allows more configurability and better localization. |
[NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehavior10_4]; |
// We start out with NSClockAndCalendarDatePickerStyle. |
[self setupDatePickerControl:NSClockAndCalendarDatePickerStyle]; |
self.minDatePicker.dateValue = [NSDate date]; |
self.maxDatePicker.dateValue = [NSDate distantFuture]; |
[self updateControls]; // Force update of all UI elements and the picker itself. |
// Listen for locale changes so we can update our formatted strings. |
_localChangedObserver = |
[[NSNotificationCenter defaultCenter] addObserverForName:NSCurrentLocaleDidChangeNotification |
object:nil |
queue:nil |
usingBlock:^(NSNotification *notification) { |
// Our Locale was changed by the user, |
// update our formatted date strings. |
[self updateDateResult]; |
}]; |
// Workaround to window resize problem with NSTabViewController on 10.13. |
self.preferredContentSize = self.view.frame.size; |
} |
// ------------------------------------------------------------------------------- |
// dealloc |
// ------------------------------------------------------------------------------- |
- (void)dealloc |
{ |
[[NSNotificationCenter defaultCenter] removeObserver:self.localChangedObserver]; |
} |
// ------------------------------------------------------------------------------- |
// setupDatePickerControl:pickerStyle |
// |
// Delete and re-create a new NSDatePicker. |
// ------------------------------------------------------------------------------- |
- (void)setupDatePickerControl:(NSDatePickerStyle)pickerStyle |
{ |
// We need to re-create the picker control (due to a resize bug when switching between styles). |
if (self.datePickerControl != nil) // Hide and release the previous date picker, if any. |
{ |
self.datePickerControl.hidden = YES; |
_datePickerControl = nil; |
} |
// Compute the frame placement and size that we want to use for the NSDatePicker. |
NSRect frame = NSMakeRect(0.0, 0.0, 295.0, 154.0); |
if (pickerStyle != NSClockAndCalendarDatePickerStyle) |
{ |
frame.size.width -= 100;// For non-clock/calendar style, we narrow it a bit. |
} |
_datePickerControl = [[NSDatePicker alloc] initWithFrame:frame]; |
// We want to be the cell's delegate to catch date validation (via validateProposedDateValue). |
self.datePickerControl.delegate = self; |
// We want to respond to date/time changes. |
self.datePickerControl.action = @selector(datePickerAction:); |
self.datePickerControl.datePickerStyle = pickerStyle; // Set our desired picker style. |
self.datePickerControl.drawsBackground = YES; |
// Always set the date/time to TODAY, note that our delete override might block this. |
self.datePickerControl.dateValue = [NSDate date]; |
[self.datePickerBox addSubview:self.datePickerControl]; |
// Sync the picker style popup with the new style change. |
[self.pickerStylePopup selectItemWithTag:pickerStyle]; |
[self.datePickerControl setNeedsDisplay:YES]; |
[self updateControls]; // Force update of all UI elements and the picker itself. |
} |
// ------------------------------------------------------------------------------- |
// updateDateResult |
// ------------------------------------------------------------------------------- |
- (void)updateDateResult |
{ |
NSDate *theDate = self.datePickerControl.dateValue; |
if (theDate != nil) |
{ |
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; |
/* Some examples: |
[formatter setDateStyle:NSDateFormatterNoStyle]; // <no date displayed> |
[formatter setDateStyle:NSDateFormatterMediumStyle]; // Jan 24, 1984 |
[formatter setDateStyle:NSDateFormatterShortStyle]; // 1/24/84 |
[formatter setDateStyle:NSDateFormatterLongStyle]; // January 24, 1984 |
[formatter setDateStyle:NSDateFormatterFullStyle]; // Tuesday, January 24, 1984 |
[formatter setTimeStyle:NSDateFormatterNoStyle]; // <no time displayed> |
[formatter setTimeStyle:NSDateFormatterShortStyle]; // 2:44 PM |
[formatter setTimeStyle:NSDateFormatterMediumStyle]; // 2:44:55 PM |
[formatter setTimeStyle:NSDateFormatterLongStyle]; // 2:44:55 PM PDT |
[formatter setTimeStyle:NSDateFormatterFullStyle]; // 2:44:55 PM PDT |
*/ |
NSString *formattedDateString; |
formatter.dateStyle = NSDateFormatterShortStyle; |
formatter.timeStyle = NSDateFormatterNoStyle; |
formattedDateString = [formatter stringFromDate:theDate]; |
self.dateResult1.stringValue = formattedDateString; |
formatter.dateStyle = NSDateFormatterShortStyle; |
formatter.timeStyle = NSDateFormatterShortStyle; |
formattedDateString = [formatter stringFromDate:theDate]; |
self.dateResult2.stringValue = formattedDateString; |
formatter.dateStyle = NSDateFormatterMediumStyle; |
formatter.timeStyle = NSDateFormatterShortStyle; |
formattedDateString = [formatter stringFromDate:theDate]; |
self.dateResult3.stringValue = formattedDateString; |
formatter.dateStyle = NSDateFormatterLongStyle; |
formatter.timeStyle = NSDateFormatterShortStyle; |
formattedDateString = [formatter stringFromDate:theDate]; |
self.dateResult4.stringValue = formattedDateString; |
formatter.dateStyle = NSDateFormatterFullStyle; |
formatter.timeStyle = NSDateFormatterFullStyle; |
formattedDateString = [formatter stringFromDate:theDate]; |
self.dateResult5.stringValue = formattedDateString; |
} |
} |
// ------------------------------------------------------------------------------- |
// updateControls |
// |
// Force update of all UI elements and the picker itself. |
// ------------------------------------------------------------------------------- |
- (void)updateControls |
{ |
[self.datePickerControl setNeedsDisplay:YES]; // Force it to update. |
[self updateDatePickerMode]; |
[self updateDateTimeElementFlags]; |
[self updateDateResult]; |
} |
#pragma mark - NSDatePicker |
// ------------------------------------------------------------------------------- |
// setPickerStyle:sender: |
// |
// User chose a different picker style from the Picker Style popup. |
// ------------------------------------------------------------------------------- |
- (IBAction)setPickerStyle:(id)sender |
{ |
// Find which style was chosen from the popup. |
NSUInteger tag = [sender selectedTag]; |
if (self.datePickerControl.datePickerStyle != tag) |
{ |
// Hide the date picker while we re-configure things. |
self.datePickerControl.hidden = YES; |
[self setupDatePickerControl:tag]; // Set our desired picker style. |
// Finished, show the data picker again. |
self.datePickerControl.hidden = NO; |
[self updateControls]; // Force update of all UI elements and the picker itself. |
} |
} |
// ------------------------------------------------------------------------------- |
// datePickerAction:sender: |
// |
// The user interacted with the date picker control so update the date/time examples. |
// ------------------------------------------------------------------------------- |
- (IBAction)datePickerAction:(id)sender |
{ |
[self updateDateResult]; |
} |
// ------------------------------------------------------------------------------- |
// dateOverrideAction:sender: |
// |
// The user checked/unchecked the "Date Override" checkbox - which in effect |
// turns on or off the delegate method to override the date. |
// ------------------------------------------------------------------------------- |
- (IBAction)dateOverrideAction:(id)sender |
{ |
// Enable/disable override data picker based on checkbox state. |
self.overrideDate.enabled = [sender state]; |
self.setToTodayButton.enabled = ![sender state]; // Can't set today when we override the date. |
// Set us up as the delegate if we want to override the date/time. |
self.datePickerControl.delegate = [sender state] ? self : nil; |
// Force the delete "datePickerCell" to be updated. |
self.datePickerControl.dateValue = [NSDate date]; |
// Date picker modes turned off for date override. |
self.singleDateMode.enabled = ![sender state]; |
self.rangeDateMode.enabled = ![sender state]; |
// Date picker in single date mode for date override. |
self.datePickerControl.datePickerMode = NSSingleDateMode; |
self.singleDateMode.state = NSControlStateValueOn; |
[self updateControls]; // Force update of all UI elements and the picker itself. |
} |
// ------------------------------------------------------------------------------- |
// setToday:sender |
// |
// Sets the date picker value to 'today'. |
// ------------------------------------------------------------------------------- |
- (IBAction)setToday:(id)sender |
{ |
self.datePickerControl.dateValue = [NSDate date]; |
} |
// ------------------------------------------------------------------------------- |
// dateOverrideChangeAction:sender |
// |
// Action method for the "overrideDate" date picker (user clicked up or down arrow). |
// ------------------------------------------------------------------------------- |
- (IBAction)dateOverrideChangeAction:(id)sender |
{ |
self.datePickerControl.dateValue = [NSDate date]; // Force the delete "datePickerCell" to be updated. |
[self updateDateResult]; |
} |
#pragma mark - Date Time Elements |
// Date/time element checkbox selections. |
typedef NS_ENUM(NSInteger, TimeElementFlags) |
{ |
hourMinuteDatePickerElementFlag = 0, |
hourMinuteSecondDatePickerElementFlag, |
}; |
typedef NS_ENUM(NSInteger, DateElementFlags) |
{ |
yearMonthDatePickerElementFlag = 0, |
yearMonthDayDatePickerElementFlag, |
}; |
// ------------------------------------------------------------------------------- |
// setDateElementFlags:sender: |
// |
// The user checked/unchecked one of the "Date Element" checkboxes. |
// ------------------------------------------------------------------------------- |
- (IBAction)setDateElementFlags:(id)sender |
{ |
NSButton *checkBox = sender; |
NSInteger tag = checkBox.tag; |
BOOL checked = checkBox.state; |
NSDatePickerElementFlags flags = self.datePickerControl.datePickerElements; |
if (self.datePickerControl.datePickerStyle == NSClockAndCalendarDatePickerStyle) |
{ |
// For clock-calender stype: month, day, year are required. |
flags |= NSYearMonthDatePickerElementFlag; |
flags |= NSYearMonthDayDatePickerElementFlag; |
} |
else |
{ |
// Picker style is either: NSTextFieldAndStepperDatePickerStyle or NSTextFieldDatePickerStyle |
// Allow for the month, day, year to be changed. |
// |
switch (tag) |
{ |
case yearMonthDatePickerElementFlag: |
// Display and allow editing of the year and month elements of the date. |
if (checked) |
flags |= NSYearMonthDatePickerElementFlag; |
else |
flags ^= NSYearMonthDatePickerElementFlag; |
break; |
case yearMonthDayDatePickerElementFlag: |
// Display and allow editing of the year, month and day elements of the date. |
if (checked) |
flags |= NSYearMonthDayDatePickerElementFlag; |
else |
flags ^= NSYearMonthDayDatePickerElementFlag; |
break; |
} |
} |
self.datePickerControl.datePickerElements = flags; |
} |
// ------------------------------------------------------------------------------- |
// setTimeElementFlags:sender: |
// |
// The user checked/unchecked one of the "Time Element" checkboxes. |
// ------------------------------------------------------------------------------- |
- (IBAction)setTimeElementFlags:(id)sender |
{ |
NSButton *checkBox = sender; |
NSInteger tag = checkBox.tag; |
BOOL checked = checkBox.state; |
NSDatePickerElementFlags flags = self.datePickerControl.datePickerElements; |
switch (tag) |
{ |
case hourMinuteDatePickerElementFlag: |
if (checked) |
flags |= NSHourMinuteDatePickerElementFlag; |
else |
flags ^= NSHourMinuteDatePickerElementFlag; |
break; |
case hourMinuteSecondDatePickerElementFlag: |
if (checked) |
flags |= NSHourMinuteSecondDatePickerElementFlag; |
else |
flags ^= NSHourMinuteSecondDatePickerElementFlag; |
break; |
} |
self.datePickerControl.datePickerElements = flags; |
} |
// ------------------------------------------------------------------------------- |
// updateDateTimeElementFlags |
// |
// Updates our checkboxes to reflect the current control flags. |
// ------------------------------------------------------------------------------- |
- (void)updateDateTimeElementFlags |
{ |
NSDatePickerElementFlags elementFlags = self.datePickerControl.datePickerElements; |
// Date elements: |
self.YearMonthElementFlag.state = (elementFlags & NSYearMonthDatePickerElementFlag) != 0; |
self.YearMonthDayElementFlag.state = (elementFlags & NSYearMonthDayDatePickerElementFlag) != 0; |
// Time elements: |
self.HourMinuteElementFlag.state = ((elementFlags & NSHourMinuteDatePickerElementFlag) != 0); |
self.HourMinuteSecondElementFlag.state = ((elementFlags & NSHourMinuteSecondDatePickerElementFlag) != 0); |
} |
#pragma mark - Picker Min Max Date |
// ------------------------------------------------------------------------------- |
// setMinDate:sender: |
// |
// User wants to set the minimum date for the picker. |
// ------------------------------------------------------------------------------- |
- (IBAction)setMinDate:(id)sender |
{ |
self.datePickerControl.minDate = self.minDatePicker.dateValue; |
} |
// ------------------------------------------------------------------------------- |
// setMaxDate:sender: |
// |
// User wants to set the maximum date for the picker. |
// ------------------------------------------------------------------------------- |
- (IBAction)setMaxDate:(id)sender |
{ |
self.datePickerControl.maxDate = self.maxDatePicker.dateValue; |
} |
#pragma mark - Picker Mode |
typedef NS_ENUM(NSInteger, DateMode) |
{ |
kSingleDateMode = 1, |
kRangeDateMode |
}; |
// ------------------------------------------------------------------------------- |
// setDatePickerMode:sender: |
// |
// User wants to change the "Date Picker Mode". |
// ------------------------------------------------------------------------------- |
- (IBAction)setDatePickerMode:(id)sender |
{ |
NSButton *chosenRadio = sender; |
switch (chosenRadio.tag) |
{ |
case kSingleDateMode: |
{ |
self.datePickerControl.datePickerMode = NSSingleDateMode; |
break; |
} |
case kRangeDateMode: |
{ |
self.datePickerControl.datePickerMode = NSRangeDateMode; |
break; |
} |
} |
[self updateControls]; // Force update of all UI elements and the picker itself. |
} |
// ------------------------------------------------------------------------------- |
// updateDatePickerMode |
// |
// Used to update the NSDatePicker's NSDatePickerMode attributes. |
// ------------------------------------------------------------------------------- |
- (void)updateDatePickerMode |
{ |
NSDatePickerMode mode = self.datePickerControl.datePickerMode; |
switch (mode) |
{ |
case NSSingleDateMode: |
{ |
// Interval value not applicable. |
self.secondsRangeEdit.enabled = NO; |
self.secondsRangeEditLabel.textColor = [NSColor lightGrayColor]; |
self.datePickerControl.timeInterval = 0; |
break; |
} |
case NSRangeDateMode: |
{ |
// Interval value applies. |
self.secondsRangeEdit.enabled = YES; |
self.secondsRangeEditLabel.textColor = [NSColor blackColor]; |
// Set the date range by start date |
// (here we use the current date in the date picker control), and time interval (in seconds). |
NSString *secsStr = self.secondsRangeEdit.stringValue; |
NSInteger numSeconds = secsStr.integerValue; |
self.datePickerControl.timeInterval = numSeconds; |
break; |
} |
} |
} |
// ------------------------------------------------------------------------------- |
// textDidEndEditing:notification |
// |
// The user finished editing the time interval (in seconds). |
// |
// This controller is a delegate to the NSTextField: number of seconds (for the date range), |
// so here we get notified when the user has finished editing the seconds range, |
// then we update the date picker control. |
// |
// NOTE: don't use "textDidEndEditing" because NSTextField is not NSText, rather is a subclass |
// of NSControl, so use the delegate methods from NSControl. |
// ------------------------------------------------------------------------------- |
- (void)controlTextDidEndEditing:(NSNotification *)notification |
{ |
[self updateDatePickerMode]; // Force update of the date picker control. |
} |
#pragma mark - NSDatePickerCellDelegate |
// ------------------------------------------------------------------------------- |
// datePickerCell:aDatePickerCell:proposedDateValue:proposedTimeInterval |
// |
// Delegate to NSDatePickerCell. |
// ------------------------------------------------------------------------------- |
- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell validateProposedDateValue:(NSDate **)proposedDateValue |
timeInterval:(NSTimeInterval*)proposedTimeInterval |
{ |
ViewController *controller = (ViewController *)aDatePickerCell.delegate; |
if ((controller == self) && (aDatePickerCell == self.datePickerControl.cell)) |
{ |
// Override the date and time? |
if (self.overrideDateCheck.cell.state) |
{ |
// Override the date using the user specified date. |
*proposedDateValue = self.overrideDate.dateValue; |
} |
} |
} |
@end |
Copyright © 2018 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2018-05-03