THDatePickerViewController.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. //
  2. // THDatePickerViewController.m
  3. // THCalendarDatePicker
  4. //
  5. // Created by chase wasden on 2/10/13.
  6. // Adapted by Hannes Tribus on 31/07/14.
  7. // Copyright (c) 2014 3Bus. All rights reserved.
  8. //
  9. #import "THDatePickerViewController.h"
  10. #import "NSDate+Difference.h"
  11. #ifdef DEBUG
  12. //static int FIRST_WEEKDAY = 2;
  13. #endif
  14. @interface THDatePickerViewController () {
  15. int _weeksOnCalendar;
  16. int _bufferDaysBeginning;
  17. int _daysInMonth;
  18. NSDate * _dateNoTime;
  19. NSCalendar * _calendar;
  20. BOOL _allowClearDate;
  21. BOOL _allowSelectionOfSelectedDate;
  22. BOOL _clearAsToday;
  23. BOOL _autoCloseOnSelectDate;
  24. NSUInteger _daysInHistory;
  25. NSUInteger _daysInFuture;
  26. BOOL _disableYearSwitch;
  27. BOOL (^_dateHasItemsCallback)(NSDate *);
  28. float _slideAnimationDuration;
  29. NSMutableArray * _selectedDates;
  30. NSMutableArray * _selectedDateViews;
  31. THDatePickerSelectionType _selectionType;
  32. }
  33. @property (nonatomic, strong) NSDate * firstOfCurrentMonth;
  34. @property (nonatomic, strong) THDateDay * currentDay;
  35. @property (nonatomic, strong) NSDate * internalDate;
  36. @property (weak, nonatomic) IBOutlet UILabel *monthLabel;
  37. @property (weak, nonatomic) IBOutlet UIButton *nextMonthBtn;
  38. @property (weak, nonatomic) IBOutlet UIButton *prevMonthBtn;
  39. @property (weak, nonatomic) IBOutlet UIButton *nextYearBtn;
  40. @property (weak, nonatomic) IBOutlet UIButton *prevYearBtn;
  41. @property (weak, nonatomic) IBOutlet UIButton *closeBtn;
  42. @property (weak, nonatomic) IBOutlet UIButton *clearBtn;
  43. @property (weak, nonatomic) IBOutlet UIButton *okBtn;
  44. @property (unsafe_unretained, nonatomic) IBOutlet UILabel *titleLabel;
  45. @property (strong, nonatomic) IBOutlet UIView *calendarDaysView;
  46. @property (weak, nonatomic) IBOutlet UIView *weekdaysView;
  47. - (IBAction)nextMonthPressed:(id)sender;
  48. - (IBAction)prevMonthPressed:(id)sender;
  49. - (IBAction)nextYearPressed:(id)sender;
  50. - (IBAction)prevYearPressed:(id)sender;
  51. - (IBAction)okPressed:(id)sender;
  52. - (IBAction)clearPressed:(id)sender;
  53. - (IBAction)closePressed:(id)sender;
  54. @end
  55. @implementation THDatePickerViewController
  56. @synthesize date = _date;
  57. @synthesize selectedBackgroundColor = _selectedBackgroundColor;
  58. @synthesize currentDateColor = _currentDateColor;
  59. @synthesize currentDateColorSelected = _currentDateColorSelected;
  60. @synthesize autoCloseCancelDelay = _autoCloseCancelDelay;
  61. @synthesize dateTimeZone = _dateTimeZone;
  62. @synthesize rounded = _rounded;
  63. @synthesize historyFutureBasedOnInternal = _historyFutureBasedOnInternal;
  64. @synthesize slideAnimationDuration = _slideAnimationDuration;
  65. @synthesize selectedDates = _selectedDates;
  66. - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
  67. self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  68. if (self) {
  69. // Custom initialization
  70. _calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
  71. [_calendar setLocale:[NSLocale currentLocale]];
  72. _allowClearDate = NO;
  73. _allowSelectionOfSelectedDate = NO;
  74. _clearAsToday = NO;
  75. _daysInFuture = NO;
  76. _daysInHistory = NO;
  77. _historyFutureBasedOnInternal = NO;
  78. _autoCloseCancelDelay = 1.0;
  79. _dateTimeZone = [NSTimeZone defaultTimeZone];
  80. _slideAnimationDuration = .5;
  81. _selectedDates = [[NSMutableArray alloc] init];
  82. _selectedDateViews = [[NSMutableArray alloc] init];
  83. _selectionType = THDatePickerSelectionTypeSingle;
  84. }
  85. return self;
  86. }
  87. +(THDatePickerViewController *)datePicker {
  88. return [[THDatePickerViewController alloc] initWithNibName:@"THDatePickerViewController" bundle:[NSBundle bundleForClass:self.class]];
  89. }
  90. -(void)setSelectionType:(THDatePickerSelectionType)type{
  91. _selectionType = type;
  92. if (type == THDatePickerSelectionTypeSingle){
  93. [self setAutoCloseOnSelectDate:true];
  94. }
  95. }
  96. - (void)setAllowClearDate:(BOOL)allow {
  97. _allowClearDate = allow;
  98. }
  99. - (void)setAllowSelectionOfSelectedDate:(BOOL)allow {
  100. _allowSelectionOfSelectedDate = allow;
  101. }
  102. - (void)setClearAsToday:(BOOL)beTodayButton {
  103. if (beTodayButton) {
  104. [self setAllowClearDate:beTodayButton];
  105. }
  106. _clearAsToday = beTodayButton;
  107. }
  108. - (void)setAutoCloseOnSelectDate:(BOOL)autoClose {
  109. if (!_allowClearDate)
  110. [self setAllowClearDate:!autoClose];
  111. _autoCloseOnSelectDate = autoClose;
  112. if (_autoCloseOnSelectDate && _selectionType != THDatePickerSelectionTypeSingle){
  113. _selectionType = THDatePickerSelectionTypeSingle;
  114. }
  115. }
  116. - (void)setDisableHistorySelection:(BOOL)disableHistorySelection {
  117. _daysInHistory = disableHistorySelection;
  118. }
  119. - (void)setDisableFutureSelection:(BOOL)disableFutureSelection {
  120. _daysInFuture = disableFutureSelection;
  121. }
  122. - (void)setDaysInHistorySelection:(NSUInteger)daysInHistory {
  123. _daysInHistory = daysInHistory;
  124. }
  125. - (void)setDaysInFutureSelection:(NSUInteger)daysInFuture {
  126. _daysInFuture = daysInFuture;
  127. }
  128. - (void)setDateRangeFrom:(NSDate *)fromDate toDate:(NSDate *)toDate {
  129. if (!self.internalDate)
  130. return;
  131. NSDate *intFromDate = [(fromDate ? fromDate : self.internalDate) dateWithOutTime];
  132. NSDate *intToDate = [(toDate ? toDate : self.internalDate) dateWithOutTime];
  133. [self setDaysInHistorySelection:[[self.internalDate dateWithOutTime] daysFromDate:intFromDate]];
  134. [self setDaysInFutureSelection:[intToDate daysFromDate:[self.internalDate dateWithOutTime]]];
  135. [self setHistoryFutureBasedOnInternal:YES];
  136. }
  137. - (void)setDisableYearSwitch:(BOOL)disableYearSwitch {
  138. _disableYearSwitch = disableYearSwitch;
  139. }
  140. #pragma mark - View Management
  141. - (void)viewDidLoad {
  142. [super viewDidLoad];
  143. [[NSNotificationCenter defaultCenter] addObserver:self
  144. selector:@selector(semiModalDidHide:)
  145. name:kSemiModalDidHideNotification
  146. object:nil];
  147. self.titleLabel.hidden = YES;
  148. [self configureButtonAppearances];
  149. if(_allowClearDate)
  150. [self showClearButton];
  151. else
  152. [self hideClearButton];
  153. [self addSwipeGestures];
  154. self.okBtn.enabled = [self shouldOkBeEnabled];
  155. // [self.okBtn setImage:([[[UIDevice currentDevice] systemVersion] floatValue] >= 8 ? [UIImage imageNamed:(_autoCloseOnSelectDate ? @"dialog_clear" : @"dialog_ok") inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] : [UIImage imageNamed:(_autoCloseOnSelectDate ? @"dialog_clear" : @"dialog_ok")]) forState:UIControlStateNormal];
  156. }
  157. - (void)viewWillAppear:(BOOL)animated {
  158. [super viewWillAppear:animated];
  159. [self redraw];
  160. }
  161. - (void)addSwipeGestureRecognizerWithDirection:(UISwipeGestureRecognizerDirection)direction {
  162. UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeGesture:)];
  163. swipeGesture.direction = direction;
  164. [self.calendarDaysView addGestureRecognizer:swipeGesture];
  165. }
  166. - (void)addSwipeGestures {
  167. if (!_disableYearSwitch) {
  168. [self addSwipeGestureRecognizerWithDirection:UISwipeGestureRecognizerDirectionUp];
  169. [self addSwipeGestureRecognizerWithDirection:UISwipeGestureRecognizerDirectionDown];
  170. }
  171. [self addSwipeGestureRecognizerWithDirection:UISwipeGestureRecognizerDirectionLeft];
  172. [self addSwipeGestureRecognizerWithDirection:UISwipeGestureRecognizerDirectionRight];
  173. }
  174. - (void)handleSwipeGesture:(UISwipeGestureRecognizer *)sender{
  175. //Gesture detect - swipe up/down , can be recognized direction
  176. if(sender.direction == UISwipeGestureRecognizerDirectionUp){
  177. [self nextMonthPressed:sender];
  178. } else if(sender.direction == UISwipeGestureRecognizerDirectionDown){
  179. [self prevMonthPressed:sender];
  180. } else if(sender.direction == UISwipeGestureRecognizerDirectionRight){
  181. [self prevYearPressed:sender];
  182. } else {
  183. [self nextYearPressed:sender];
  184. }
  185. }
  186. - (void)configureButtonAppearances {
  187. [super viewDidLoad];
  188. [[self.nextMonthBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  189. [[self.prevMonthBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  190. [[self.nextYearBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  191. [[self.prevYearBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  192. [[self.clearBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  193. [[self.closeBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  194. [[self.okBtn imageView] setContentMode: UIViewContentModeScaleAspectFit];
  195. UIImage * img = [self imageOfColor:[UIColor colorWithWhite:.8 alpha:1]];
  196. [self.clearBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  197. [self.closeBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  198. [self.okBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  199. img = [self imageOfColor:[UIColor colorWithWhite:.94 alpha:1]];
  200. [self.nextMonthBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  201. [self.prevMonthBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  202. [self.nextYearBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  203. [self.prevYearBtn setBackgroundImage:img forState:UIControlStateHighlighted];
  204. // Hide Buttons if not active
  205. [self.nextMonthBtn setHidden:_disableYearSwitch];
  206. [self.prevMonthBtn setHidden:_disableYearSwitch];
  207. }
  208. - (UIImage *)imageOfColor:(UIColor *)color {
  209. CGRect rect = CGRectMake(0, 0, 1, 1);
  210. UIGraphicsBeginImageContext(rect.size);
  211. CGContextRef context = UIGraphicsGetCurrentContext();
  212. CGContextSetFillColorWithColor(context,[color CGColor]);
  213. CGContextFillRect(context, rect);
  214. UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
  215. UIGraphicsEndImageContext();
  216. return img;
  217. }
  218. - (void)setDateHasItemsCallback:(BOOL (^)(NSDate * date))callback {
  219. _dateHasItemsCallback = callback;
  220. }
  221. #pragma mark - Callbacks
  222. - (void)semiModalDidHide:(NSNotification *)notification {
  223. if ([self.delegate respondsToSelector:@selector(datePickerDidHide:)]) {
  224. [self.delegate datePickerDidHide:self];
  225. }
  226. }
  227. #pragma mark - Redraw Dates
  228. - (void)redraw {
  229. if(!self.firstOfCurrentMonth) [self setDisplayedMonthFromDate:[NSDate date]];
  230. for(UIView * view in self.calendarDaysView.subviews){ // clean view
  231. [view removeFromSuperview];
  232. }
  233. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  234. NSCalendar* cal =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  235. [cal setLocale:[NSLocale currentLocale]];
  236. [formatter setCalendar:cal];
  237. [formatter setDateFormat:(_disableYearSwitch ? @"MMMM yyyy" : @"yyyy\nMMMM")];
  238. formatter.locale=[NSLocale currentLocale];
  239. NSString *monthName = [formatter stringFromDate:self.firstOfCurrentMonth];
  240. self.monthLabel.text = monthName;
  241. if (self.dateTitle != nil && _allowClearDate == NO) {
  242. self.titleLabel.text = self.dateTitle;
  243. self.titleLabel.hidden = NO;
  244. }
  245. [self redrawDays];
  246. }
  247. - (void)redrawDays {
  248. NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
  249. [offsetComponents setDay:-_bufferDaysBeginning];
  250. NSDate * date = [_calendar dateByAddingComponents:offsetComponents toDate:self.firstOfCurrentMonth options:0];
  251. [offsetComponents setDay:1];
  252. UIView * container = self.calendarDaysView;
  253. CGRect containerFrame = container.frame;
  254. int areaWidth = kScreenWidth;
  255. int areaHeight = containerFrame.size.height;
  256. int cellWidth = areaWidth/7;
  257. int cellHeight = areaHeight/_weeksOnCalendar;
  258. int days = _weeksOnCalendar*7;
  259. int curY = (areaHeight - cellHeight*_weeksOnCalendar)/2;
  260. int origX = (areaWidth - cellWidth*7)/2;
  261. int curX = origX;
  262. [self redrawWeekdays:cellWidth];
  263. for(int i = 0; i < days; i++){
  264. // @beginning
  265. if(i && !(i%7)) {
  266. curX = origX;
  267. curY += cellHeight;
  268. }
  269. THDateDay * day = [[[NSBundle bundleForClass:self.class] loadNibNamed:@"THDateDay" owner:self options:nil] objectAtIndex:0];
  270. if ([self isRounded]) {
  271. [day setRounded:YES];
  272. }
  273. day.frame = CGRectMake(curX, curY, cellWidth, cellHeight);
  274. day.delegate = self;
  275. day.date = [date dateByAddingTimeInterval:0];
  276. if (self.currentDateColor)
  277. [day setCurrentDateColor:self.currentDateColor];
  278. if (self.currentDateColorSelected)
  279. [day setCurrentDateColorSelected:self.currentDateColorSelected];
  280. if (self.selectedBackgroundColor)
  281. [day setSelectedBackgroundColor:self.selectedBackgroundColor];
  282. switch (_selectionType) {
  283. case THDatePickerSelectionTypeSingle:
  284. if (_internalDate && ![[self dateWithOutTime:date] timeIntervalSinceDate:_internalDate]) {
  285. self.currentDay = day;
  286. [day setSelected:YES];
  287. }
  288. break;
  289. case THDatePickerSelectionTypeMulti:
  290. if ([_selectedDates containsObject:date]) {
  291. if ([_selectedDates lastObject] == date) {
  292. _internalDate = date;
  293. }
  294. [day setSelected:YES];
  295. } else {
  296. [day setSelected:NO];
  297. }
  298. break;
  299. case THDatePickerSelectionTypeRange:
  300. if ([_selectedDates containsObject:date]) {
  301. [day setSelected:YES];
  302. } else if ([date timeIntervalSinceDate:[_selectedDates firstObject]] > 0 && [date timeIntervalSinceDate:[_selectedDates lastObject]] < 0){
  303. [day setIsDayInRange];
  304. }else {
  305. [day setSelected:NO];
  306. }
  307. break;
  308. }
  309. [day setLightText:![self dateInCurrentMonth:date]];
  310. [day setEnabled:![self dateInFutureAndShouldBeDisabled:date]];
  311. [day indicateDayHasItems:(_dateHasItemsCallback && _dateHasItemsCallback(date))];
  312. NSDateComponents *comps = [_calendar components:NSCalendarUnitDay fromDate:date];
  313. [day.dateButton setTitle:[NSString stringWithFormat:@"%ld",(long)[comps day]]
  314. forState:UIControlStateNormal];
  315. [self.calendarDaysView addSubview:day];
  316. // @end
  317. date = [_calendar dateByAddingComponents:offsetComponents toDate:date options:0];
  318. curX += cellWidth;
  319. }
  320. }
  321. - (void)redrawWeekdays:(int)dayWidth {
  322. if(!self.weekdaysView.subviews.count) {
  323. CGSize fullSize = CGSizeMake(kScreenWidth, self.weekdaysView.frame.size.height);
  324. int curX = (fullSize.width - 7*dayWidth)/2;
  325. //NSCalendar *c = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  326. NSCalendar* c =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  327. [c setLocale:[NSLocale currentLocale]];
  328. NSDateFormatter *df = [[NSDateFormatter alloc] init];
  329. NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
  330. [offsetComponents setDay:1];
  331. [df setDateFormat:@"EE"];
  332. df.locale = [NSLocale currentLocale];
  333. NSCalendar* cal =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  334. [cal setLocale:[NSLocale currentLocale]];
  335. [df setCalendar:cal];
  336. NSDateComponents *firstDayOffsetComponents = [[NSDateComponents alloc] init];
  337. [firstDayOffsetComponents setDay:-_bufferDaysBeginning];
  338. NSDate * date = [_calendar dateByAddingComponents:firstDayOffsetComponents toDate:self.firstOfCurrentMonth options:0];
  339. for(int i = 0; i < 7; i++){
  340. UILabel * dayLabel = [[UILabel alloc] initWithFrame:CGRectMake(curX, 0, dayWidth, fullSize.height)];
  341. dayLabel.textAlignment = NSTextAlignmentCenter;
  342. dayLabel.font = [UIFont systemFontOfSize:12];
  343. [self.weekdaysView addSubview:dayLabel];
  344. dayLabel.text = [df stringFromDate:date];
  345. dayLabel.textColor = [UIColor grayColor];
  346. date = [_calendar dateByAddingComponents:offsetComponents toDate:date options:0];
  347. curX+=dayWidth;
  348. }
  349. }
  350. }
  351. #pragma mark - Date Set, etc.
  352. - (NSArray *)selectedDates {
  353. return [[NSArray alloc] initWithArray:_selectedDates];
  354. }
  355. - (void)setSelectedDates:(NSArray *)selectedDates {
  356. _selectedDates = [[NSMutableArray alloc] init];
  357. for (NSDate* selectedDate in selectedDates) {
  358. [_selectedDates addObject:[self dateWithOutTime:selectedDate]];
  359. }
  360. if ([_selectedDates count] > 0) {
  361. _date = (NSDate*)[_selectedDates objectAtIndex:0];
  362. _dateNoTime = !_date ? nil : [self dateWithOutTime:_date];
  363. self.internalDate = [_dateNoTime dateByAddingTimeInterval:0];
  364. }
  365. [self redrawDays];
  366. }
  367. - (void)setDate:(NSDate *)date {
  368. [self setSelectedDates:@[date]];
  369. }
  370. - (NSDate *)date {
  371. if(!self.internalDate)
  372. return nil;
  373. else if(!_date)
  374. return self.internalDate;
  375. else {
  376. int ymd = NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay;
  377. NSDateComponents* internalComps = [_calendar components:ymd fromDate:self.internalDate];
  378. int time = NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond|NSCalendarUnitTimeZone;
  379. NSDateComponents* origComps = [_calendar components:time fromDate:((_selectionType != THDatePickerSelectionTypeSingle) && [_selectedDates count] > 0 ? [_selectedDates lastObject] : _date)];
  380. [origComps setDay:[internalComps day]];
  381. [origComps setMonth:[internalComps month]];
  382. [origComps setYear:[internalComps year]];
  383. return [_calendar dateFromComponents:origComps];
  384. }
  385. }
  386. - (BOOL)shouldOkBeEnabled {
  387. switch (_selectionType) {
  388. case THDatePickerSelectionTypeSingle:
  389. if (_autoCloseOnSelectDate)
  390. return YES;
  391. NSLog(@"interval %f",[self.internalDate timeIntervalSinceDate:_dateNoTime]);
  392. return (self.internalDate && _dateNoTime && (_allowSelectionOfSelectedDate || [self.internalDate timeIntervalSinceDate:_dateNoTime]))
  393. || (self.internalDate && !_dateNoTime)
  394. || (!self.internalDate && _dateNoTime);
  395. break;
  396. case THDatePickerSelectionTypeMulti:
  397. return ([_selectedDates count] > 0);
  398. break;
  399. case THDatePickerSelectionTypeRange:
  400. return ([_selectedDates count] > 0);
  401. break;
  402. }
  403. }
  404. - (void)setInternalDate:(NSDate *)internalDate{
  405. _internalDate = internalDate;
  406. self.clearBtn.enabled = internalDate? YES : NO;
  407. self.okBtn.enabled = [self shouldOkBeEnabled];
  408. if(internalDate){
  409. [self setDisplayedMonthFromDate:internalDate];
  410. } else {
  411. [self.currentDay setSelected:NO];
  412. self.currentDay = nil;
  413. }
  414. }
  415. - (void)setDisplayedMonth:(int)month year:(int)year{
  416. NSDateFormatter *df = [[NSDateFormatter alloc] init];
  417. [df setDateFormat:@"yyyy-MM"];
  418. [df setTimeZone:self.dateTimeZone];
  419. NSCalendar* cal =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  420. [cal setLocale:[NSLocale currentLocale]];
  421. [df setCalendar:cal];
  422. self.firstOfCurrentMonth = [df dateFromString: [NSString stringWithFormat:@"%d-%@%d", year, (month<10?@"0":@""), month]];
  423. [self storeDateInformation];
  424. if ([self.delegate respondsToSelector:@selector(datePicker:changedMonth:year:)]) {
  425. [self.delegate datePicker:self changedMonth:month year:year];
  426. }
  427. }
  428. - (void)setDisplayedMonthFromDate:(NSDate *)date{
  429. NSCalendar* cal =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  430. [cal setLocale:[NSLocale currentLocale]];
  431. NSDateComponents* comps = [cal components:NSCalendarUnitYear|NSCalendarUnitMonth fromDate:date];
  432. [self setDisplayedMonth:(int)[comps month] year:(int)[comps year]];
  433. }
  434. - (void)storeDateInformation{
  435. NSDateComponents *comps = [_calendar components:NSCalendarUnitWeekday | NSCalendarUnitDay fromDate:self.firstOfCurrentMonth];
  436. NSCalendar* c =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  437. [c setLocale:[NSLocale currentLocale]];
  438. //NSCalendar *c = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  439. #ifdef DEBUG
  440. //[c setFirstWeekday:FIRST_WEEKDAY];
  441. #endif
  442. NSRange days = [c rangeOfUnit:NSCalendarUnitDay
  443. inUnit:NSCalendarUnitMonth
  444. forDate:self.firstOfCurrentMonth];
  445. int bufferDaysBeginning = (int)([comps weekday]-[c firstWeekday]);
  446. // % 7 is not working for negative numbers
  447. // http://stackoverflow.com/questions/989943/weird-objective-c-mod-behavior-for-negative-numbers
  448. if (bufferDaysBeginning < 0)
  449. bufferDaysBeginning += 7;
  450. int daysInMonthWithBuffer = (int)(days.length + bufferDaysBeginning);
  451. int numberOfWeeks = daysInMonthWithBuffer / 7;
  452. if(daysInMonthWithBuffer % 7) numberOfWeeks++;
  453. _weeksOnCalendar = 6;
  454. _bufferDaysBeginning = bufferDaysBeginning;
  455. _daysInMonth = (int)days.length;
  456. }
  457. - (void)incrementMonth:(int)incrValue{
  458. NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
  459. [offsetComponents setMonth:incrValue];
  460. NSDate * incrementedMonth = [_calendar dateByAddingComponents:offsetComponents toDate:self.firstOfCurrentMonth options:0];
  461. [self setDisplayedMonthFromDate:incrementedMonth];
  462. }
  463. - (BOOL)setDateTimeZoneWithName:(NSString *)name {
  464. NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:name];
  465. if (timeZone) {
  466. [self setDateTimeZone:timeZone];
  467. return YES;
  468. }
  469. return NO;
  470. }
  471. #pragma mark - User Events
  472. - (void)dateDayTapped:(THDateDay *)dateDay {
  473. BOOL dateInDifferentMonth = ![self dateInCurrentMonth:dateDay.date];
  474. NSDate *firstOfCurrentMonth = self.firstOfCurrentMonth;
  475. switch (_selectionType) {
  476. case THDatePickerSelectionTypeSingle:
  477. if (!_internalDate || [_internalDate timeIntervalSinceDate:dateDay.date] || _allowSelectionOfSelectedDate) { // new date selected
  478. [self.currentDay setSelected:NO];
  479. [self.currentDay setLightText:![self dateInCurrentMonth:self.currentDay.date]];
  480. [dateDay setSelected:YES];
  481. [self setInternalDate:dateDay.date];
  482. [self setCurrentDay:dateDay];
  483. [_selectedDates removeAllObjects];
  484. [_selectedDates addObject:[self dateWithOutTime:dateDay.date]];
  485. if ([self.delegate respondsToSelector:@selector(datePicker:selectedDate:)]) {
  486. [self.delegate datePicker:self selectedDate:dateDay.date];
  487. }
  488. }
  489. break;
  490. case THDatePickerSelectionTypeMulti:
  491. [self setInternalDate:dateDay.date];
  492. if (![_selectedDates containsObject:[self dateWithOutTime:dateDay.date]]){
  493. [_selectedDates addObject:[self dateWithOutTime:dateDay.date]];
  494. [dateDay setSelected:YES];
  495. if ([self.delegate respondsToSelector:@selector(datePicker:selectedDate:)]) {
  496. [self.delegate datePicker:self selectedDate:dateDay.date];
  497. }
  498. } else {
  499. if ([self.delegate respondsToSelector:@selector(datePicker:deselectedDate:)]) {
  500. [self.delegate datePicker:self deselectedDate:dateDay.date];
  501. }
  502. [dateDay setLightText:dateInDifferentMonth];
  503. [dateDay setSelected:NO];
  504. [_selectedDates removeObject:dateDay.date];
  505. }
  506. self.okBtn.enabled = [self shouldOkBeEnabled];
  507. break;
  508. case THDatePickerSelectionTypeRange:
  509. [self setInternalDate:dateDay.date];
  510. if ([_selectedDates count] > 1){
  511. for (NSUInteger i = 0; i < [_selectedDates count]; i++){
  512. NSDate *date = [_selectedDates objectAtIndex:i];
  513. if ([self.delegate respondsToSelector:@selector(datePicker:deselectedDate:)]) {
  514. [self.delegate datePicker:self deselectedDate:date];
  515. }
  516. }
  517. [_selectedDates removeAllObjects];
  518. }
  519. [_selectedDates addObject:[self dateWithOutTime:dateDay.date]];
  520. if ([self.delegate respondsToSelector:@selector(datePicker:selectedDate:)]) {
  521. [self.delegate datePicker:self selectedDate:dateDay.date];
  522. }
  523. [dateDay setSelected:YES];
  524. if ([_selectedDates count] > 1){
  525. [_selectedDates sortUsingSelector:@selector(compare:)];
  526. }
  527. [self redrawDays];
  528. self.okBtn.enabled = [self shouldOkBeEnabled];
  529. break;
  530. }
  531. if (_autoCloseOnSelectDate) {
  532. [self.delegate datePickerDonePressed:self];
  533. }
  534. if (dateInDifferentMonth) {
  535. [self slideTransitionViewInDirection:[dateDay.date timeIntervalSinceDate:firstOfCurrentMonth]<0 ? UISwipeGestureRecognizerDirectionRight : UISwipeGestureRecognizerDirectionLeft];
  536. }
  537. }
  538. - (void)slideTransitionViewInDirection:(UISwipeGestureRecognizerDirection)dir {
  539. CGRect origFrame = self.calendarDaysView.frame;
  540. CGRect outDestFrame = origFrame;
  541. CGRect inStartFrame = origFrame;
  542. switch (dir) {
  543. case UISwipeGestureRecognizerDirectionUp:
  544. //outDestFrame.origin.y -= self.calendarDaysView.frame.size.height;
  545. inStartFrame.origin.y += self.calendarDaysView.frame.size.height;
  546. break;
  547. case UISwipeGestureRecognizerDirectionDown:
  548. outDestFrame.origin.y += self.calendarDaysView.frame.size.height;
  549. //inStartFrame.origin.y -= self.calendarDaysView.frame.size.height;
  550. break;
  551. case UISwipeGestureRecognizerDirectionLeft:
  552. outDestFrame.origin.x -= self.calendarDaysView.frame.size.width;
  553. inStartFrame.origin.x += self.calendarDaysView.frame.size.width;
  554. break;
  555. default:
  556. outDestFrame.origin.x += self.calendarDaysView.frame.size.width;
  557. inStartFrame.origin.x -= self.calendarDaysView.frame.size.width;
  558. break;
  559. }
  560. UIView *oldView = self.calendarDaysView;
  561. UIView *newView = self.calendarDaysView = [[UIView alloc] initWithFrame:inStartFrame];
  562. [oldView.superview addSubview:newView];
  563. [self addSwipeGestures];
  564. newView.alpha = 0;
  565. [self redraw];
  566. [oldView.superview layoutSubviews];
  567. [UIView animateWithDuration:self.slideAnimationDuration animations:^{
  568. newView.frame = origFrame;
  569. newView.alpha = 1;
  570. oldView.frame = outDestFrame;
  571. oldView.alpha = 0;
  572. } completion:^(BOOL finished) {
  573. [oldView removeFromSuperview];
  574. }];
  575. }
  576. /* If _disableYearSwitch is true, then only the month switches are active, and they'll swipe left and right only */
  577. - (IBAction)nextMonthPressed:(id)sender {
  578. [self incrementMonth:1];
  579. [self slideTransitionViewInDirection:(_disableYearSwitch ? UISwipeGestureRecognizerDirectionLeft : UISwipeGestureRecognizerDirectionUp)];
  580. }
  581. - (IBAction)prevMonthPressed:(id)sender {
  582. [self incrementMonth:-1];
  583. [self slideTransitionViewInDirection:(_disableYearSwitch ? UISwipeGestureRecognizerDirectionRight : UISwipeGestureRecognizerDirectionDown)];
  584. }
  585. - (IBAction)nextYearPressed:(id)sender {
  586. [self incrementMonth:(_disableYearSwitch ? 1 : 12)];
  587. [self slideTransitionViewInDirection:UISwipeGestureRecognizerDirectionLeft];
  588. }
  589. - (IBAction)prevYearPressed:(id)sender {
  590. [self incrementMonth:(_disableYearSwitch ? -1 : -12)];
  591. [self slideTransitionViewInDirection:UISwipeGestureRecognizerDirectionRight];
  592. }
  593. - (IBAction)okPressed:(id)sender {
  594. if(self.okBtn.enabled) {
  595. [self.delegate datePickerDonePressed:self];
  596. }
  597. }
  598. - (IBAction)clearPressed:(id)sender {
  599. if(self.clearBtn.enabled){
  600. if (_clearAsToday) {
  601. [self setDate:[NSDate date]];
  602. [self redraw];
  603. if (_autoCloseOnSelectDate) {
  604. [self.okBtn setUserInteractionEnabled:NO];
  605. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.autoCloseCancelDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  606. [self.delegate datePickerDonePressed:self];
  607. [self.okBtn setUserInteractionEnabled:YES];
  608. });
  609. }
  610. } else {
  611. self.internalDate = nil;
  612. [self.currentDay setSelected:NO];
  613. self.currentDay = nil;
  614. }
  615. }
  616. }
  617. - (IBAction)closePressed:(id)sender {
  618. [self.delegate datePickerCancelPressed:self];
  619. }
  620. #pragma mark - Hide/Show Clear Button
  621. - (void) showClearButton {
  622. int width = kScreenWidth;
  623. int buttonHeight = _toolbarBackgroundView.height;
  624. int buttonWidth = (width-20)/3;
  625. int curX = (width - buttonWidth*3 - 10)/2;
  626. self.closeBtn.frame = CGRectMake(curX, 0, buttonWidth, buttonHeight);
  627. curX+=buttonWidth+5;
  628. self.clearBtn.frame = CGRectMake(curX, 0, buttonWidth, buttonHeight);
  629. curX+=buttonWidth+5;
  630. self.okBtn.frame = CGRectMake(curX, 0, buttonWidth, buttonHeight);
  631. if (_clearAsToday) {
  632. [self.clearBtn setImage:nil forState:UIControlStateNormal];
  633. [self.clearBtn setTitle:NSLocalizedString(@"TODAY", @"Customize this for your language") forState:UIControlStateNormal];
  634. } else {
  635. [self.clearBtn setImage:([[[UIDevice currentDevice] systemVersion] floatValue] >= 8 ? [UIImage imageNamed:@"dialog_clear" inBundle:[NSBundle bundleForClass:self.class] compatibleWithTraitCollection:nil] : [UIImage imageNamed:@"dialog_clear"]) forState:UIControlStateNormal];
  636. }
  637. }
  638. - (void) hideClearButton {
  639. int width = kScreenWidth;
  640. int buttonHeight = _toolbarBackgroundView.height;
  641. self.clearBtn.hidden = YES;
  642. int buttonWidth = (width-15)/2;
  643. int curX = (width - buttonWidth*2 - 5)/2;
  644. self.closeBtn.frame = CGRectMake(curX, 0, buttonWidth, buttonHeight);
  645. curX+=buttonWidth+5;
  646. self.okBtn.frame = CGRectMake(curX, 0, buttonWidth, buttonHeight);
  647. }
  648. #pragma mark - Date Utils
  649. - (BOOL)dateInFutureAndShouldBeDisabled:(NSDate *)dateToCompare {
  650. NSDate *currentDate = [(self.isHistoryFutureBasedOnInternal ?
  651. self.internalDate : [NSDate date]) dateWithOutTime];
  652. NSInteger dayDifference = [currentDate daysFromDate:dateToCompare];
  653. NSCalendar* calendar =[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  654. [calendar setLocale:[NSLocale currentLocale]];
  655. //NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
  656. NSInteger comps = (NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear);
  657. currentDate = [calendar dateFromComponents:[calendar components:comps fromDate:currentDate]];
  658. dateToCompare = [calendar dateFromComponents:[calendar components:comps fromDate:dateToCompare]];
  659. NSComparisonResult compResult = [currentDate compare:dateToCompare];
  660. return (compResult == NSOrderedDescending && _daysInHistory && _daysInHistory <= dayDifference) || (compResult == NSOrderedAscending && _daysInFuture && _daysInFuture <= dayDifference);
  661. }
  662. - (BOOL)dateInCurrentMonth:(NSDate *)date{
  663. unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
  664. NSDateComponents* comp1 = [_calendar components:unitFlags fromDate:self.firstOfCurrentMonth];
  665. NSDateComponents* comp2 = [_calendar components:unitFlags fromDate:date];
  666. return [comp1 year] == [comp2 year] && [comp1 month] == [comp2 month];
  667. }
  668. - (NSDate *)dateWithOutTime:(NSDate *)datDate {
  669. if(!datDate) {
  670. datDate = [NSDate date];
  671. }
  672. NSDateComponents* comps = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:datDate];
  673. return [[NSCalendar currentCalendar] dateFromComponents:comps];
  674. }
  675. #pragma mark - Cleanup
  676. - (void)didReceiveMemoryWarning {
  677. [super didReceiveMemoryWarning];
  678. }
  679. - (void)dealloc {
  680. [[NSNotificationCenter defaultCenter] removeObserver:self];
  681. }
  682. @end