From 7f4c008504d45105edae39a6fc374d421d42668c Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 20 Jan 2015 15:34:42 +0000 Subject: [PATCH 1/7] Added initial support for multiple selection mode. --- TimesSquare/TSQCalendarRowCell.h | 1 + TimesSquare/TSQCalendarRowCell.m | 152 +++++++++++++----- TimesSquare/TSQCalendarView.h | 47 +++++- TimesSquare/TSQCalendarView.m | 73 ++++++++- TimesSquareTestApp/TSQTAAppDelegate.m | 10 +- TimesSquareTestApp/TSQTAViewController.h | 1 + TimesSquareTestApp/TSQTAViewController.m | 94 +++++++++-- .../project.pbxproj | 20 ++- 8 files changed, 341 insertions(+), 57 deletions(-) diff --git a/TimesSquare/TSQCalendarRowCell.h b/TimesSquare/TSQCalendarRowCell.h index 0fad589..8e68687 100644 --- a/TimesSquare/TSQCalendarRowCell.h +++ b/TimesSquare/TSQCalendarRowCell.h @@ -65,4 +65,5 @@ */ - (void)selectColumnForDate:(NSDate *)date; + @end diff --git a/TimesSquare/TSQCalendarRowCell.m b/TimesSquare/TSQCalendarRowCell.m index 0cf087b..5653527 100644 --- a/TimesSquare/TSQCalendarRowCell.m +++ b/TimesSquare/TSQCalendarRowCell.m @@ -16,10 +16,10 @@ @interface TSQCalendarRowCell () @property (nonatomic, strong) NSArray *dayButtons; @property (nonatomic, strong) NSArray *notThisMonthButtons; @property (nonatomic, strong) UIButton *todayButton; -@property (nonatomic, strong) UIButton *selectedButton; +@property (nonatomic, strong) NSArray *selectedButtons; @property (nonatomic, assign) NSInteger indexOfTodayButton; -@property (nonatomic, assign) NSInteger indexOfSelectedButton; +@property (nonatomic, strong) NSMutableIndexSet *indexesOfSelectedButtons; @property (nonatomic, strong) NSDateFormatter *dayFormatter; @property (nonatomic, strong) NSDateFormatter *accessibilityFormatter; @@ -39,9 +39,23 @@ - (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseI return nil; } + _indexesOfSelectedButtons = [NSMutableIndexSet indexSet]; + return self; } + +- (void)prepareForReuse +{ + [super prepareForReuse]; + + [self.selectedButtons enumerateObjectsUsingBlock:^(UIButton *obj, NSUInteger idx, BOOL *stop) { + obj.hidden = YES; + }]; + + self.todayButton.hidden = YES; +} + - (void)configureButton:(UIButton *)button; { button.titleLabel.font = [UIFont boldSystemFontOfSize:19.f]; @@ -96,32 +110,41 @@ - (void)createTodayButton; self.todayButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); } -- (void)createSelectedButton; +- (void)createSelectedButtons; { - self.selectedButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; - [self.contentView addSubview:self.selectedButton]; - [self configureButton:self.selectedButton]; - - [self.selectedButton setAccessibilityTraits:UIAccessibilityTraitSelected|self.selectedButton.accessibilityTraits]; - - self.selectedButton.enabled = NO; - [self.selectedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; - [self.selectedButton setBackgroundImage:[self selectedBackgroundImage] forState:UIControlStateNormal]; - [self.selectedButton setTitleShadowColor:[UIColor colorWithWhite:0.0f alpha:0.75f] forState:UIControlStateNormal]; + NSMutableArray *selectedButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; + for (NSUInteger index = 0; index < self.daysInWeek; index++) { + + UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + button.hidden = YES; + [self.contentView addSubview:button]; + [self configureButton:button]; + + [button setAccessibilityTraits:UIAccessibilityTraitSelected|button.accessibilityTraits]; + + button.enabled = NO; + [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [button setBackgroundImage:[self selectedBackgroundImage] forState:UIControlStateNormal]; + [button setTitleShadowColor:[UIColor colorWithWhite:0.0f alpha:0.75f] forState:UIControlStateNormal]; + + button.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); + + [selectedButtons addObject:button]; + } - self.selectedButton.titleLabel.shadowOffset = CGSizeMake(0.0f, -1.0f / [UIScreen mainScreen].scale); - self.indexOfSelectedButton = -1; + self.selectedButtons = selectedButtons; + self.indexesOfSelectedButtons = [NSMutableIndexSet indexSet]; } - (void)setBeginningDate:(NSDate *)date; { _beginningDate = date; - + if (!self.dayButtons) { [self createDayButtons]; [self createNotThisMonthButtons]; [self createTodayButton]; - [self createSelectedButton]; + [self createSelectedButtons]; } NSDateComponents *offset = [NSDateComponents new]; @@ -129,8 +152,7 @@ - (void)setBeginningDate:(NSDate *)date; self.todayButton.hidden = YES; self.indexOfTodayButton = -1; - self.selectedButton.hidden = YES; - self.indexOfSelectedButton = -1; + [self.indexesOfSelectedButtons removeAllIndexes]; for (NSUInteger index = 0; index < self.daysInWeek; index++) { NSString *title = [self.dayFormatter stringFromDate:date]; @@ -186,7 +208,21 @@ - (IBAction)dateButtonPressed:(id)sender; NSDateComponents *offset = [NSDateComponents new]; offset.day = [self.dayButtons indexOfObject:sender]; NSDate *selectedDate = [self.calendar dateByAddingComponents:offset toDate:self.beginningDate options:0]; - self.calendarView.selectedDate = selectedDate; + + if (self.calendarView.selectionMode == TSQSelectionModeMultiple) { + + NSMutableArray *tmp = [self.calendarView.selectedDates mutableCopy]; + + if ([tmp containsObject:selectedDate]) { + [tmp removeObject:selectedDate]; + } else { + [tmp addObject:selectedDate]; + } + + self.calendarView.selectedDates = tmp; + } else { + self.calendarView.selectedDate = selectedDate; + } } - (IBAction)todayButtonPressed:(id)sender; @@ -194,7 +230,21 @@ - (IBAction)todayButtonPressed:(id)sender; NSDateComponents *offset = [NSDateComponents new]; offset.day = self.indexOfTodayButton; NSDate *selectedDate = [self.calendar dateByAddingComponents:offset toDate:self.beginningDate options:0]; - self.calendarView.selectedDate = selectedDate; + + if (self.calendarView.selectionMode == TSQSelectionModeMultiple) { + + NSMutableArray *tmp = [self.calendarView.selectedDates mutableCopy]; + + if ([tmp containsObject:selectedDate]) { + [tmp removeObject:selectedDate]; + } else { + [tmp addObject:selectedDate]; + } + + self.calendarView.selectedDates = tmp; + } else { + self.calendarView.selectedDate = selectedDate; + } } - (void)layoutSubviews; @@ -219,38 +269,68 @@ - (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; if (self.indexOfTodayButton == (NSInteger)index) { self.todayButton.frame = rect; } - if (self.indexOfSelectedButton == (NSInteger)index) { - self.selectedButton.frame = rect; - } + + [self.indexesOfSelectedButtons enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) { + if (idx == index) { + [self.selectedButtons[idx] setFrame:rect]; + } + }]; } - (void)selectColumnForDate:(NSDate *)date; { - if (!date && self.indexOfSelectedButton == -1) { + if (!date && self.indexesOfSelectedButtons == nil) { return; } - NSInteger newIndexOfSelectedButton = -1; + NSInteger newIndexOfSelectedButton = NSNotFound; if (date) { NSInteger thisDayMonth = [self.calendar components:NSMonthCalendarUnit fromDate:date].month; if (self.monthOfBeginningDate == thisDayMonth) { newIndexOfSelectedButton = [self.calendar components:NSDayCalendarUnit fromDate:self.beginningDate toDate:date options:0].day; - if (newIndexOfSelectedButton >= (NSInteger)self.daysInWeek) { - newIndexOfSelectedButton = -1; + if (newIndexOfSelectedButton >= (NSInteger)self.daysInWeek || newIndexOfSelectedButton < 0) { + newIndexOfSelectedButton = NSNotFound; } } } + + // if this row has no selected date then unselect all dates + if (newIndexOfSelectedButton == NSNotFound) { + if (self.calendarView.selectionMode == TSQSelectionModeSingle) { + [self.selectedButtons enumerateObjectsUsingBlock:^(UIButton *obj, NSUInteger idx, BOOL *stop) { + obj.hidden = YES; + }]; + } + return; + } - self.indexOfSelectedButton = newIndexOfSelectedButton; + UIButton *button = self.selectedButtons[newIndexOfSelectedButton]; - if (newIndexOfSelectedButton >= 0) { - self.selectedButton.hidden = NO; - NSString *newTitle = [self.dayButtons[newIndexOfSelectedButton] currentTitle]; - [self.selectedButton setTitle:newTitle forState:UIControlStateNormal]; - [self.selectedButton setTitle:newTitle forState:UIControlStateDisabled]; - [self.selectedButton setAccessibilityLabel:[self.dayButtons[newIndexOfSelectedButton] accessibilityLabel]]; + // remove previous selection if single selection mode + if (self.calendarView.selectionMode == TSQSelectionModeSingle && self.indexesOfSelectedButtons.count > 0) { + UIButton *previousSelectedButton = self.selectedButtons[self.indexesOfSelectedButtons.firstIndex]; + previousSelectedButton.hidden = YES; + + // remove all indexes + [self.indexesOfSelectedButtons removeAllIndexes]; + } + + if ([self.indexesOfSelectedButtons containsIndex:newIndexOfSelectedButton]) { + // remove index and un-select button + [self.indexesOfSelectedButtons removeIndex:newIndexOfSelectedButton]; + + button.hidden = YES; + } else { - self.selectedButton.hidden = YES; + // add index and select button + [self.indexesOfSelectedButtons addIndex:newIndexOfSelectedButton]; + + NSString *newTitle = [self.dayButtons[newIndexOfSelectedButton] currentTitle]; + [button setTitle:newTitle forState:UIControlStateNormal]; + [button setTitle:newTitle forState:UIControlStateDisabled]; + [button setAccessibilityLabel:[self.dayButtons[newIndexOfSelectedButton] accessibilityLabel]]; + + button.hidden = NO; } [self setNeedsLayout]; diff --git a/TimesSquare/TSQCalendarView.h b/TimesSquare/TSQCalendarView.h index d6f0dab..6117fbd 100644 --- a/TimesSquare/TSQCalendarView.h +++ b/TimesSquare/TSQCalendarView.h @@ -10,6 +10,12 @@ #import +typedef NS_ENUM(NSInteger, TSQSelectionMode) { + TSQSelectionModeSingle, + TSQSelectionModeMultiple +}; + + @protocol TSQCalendarViewDelegate; @@ -35,13 +41,29 @@ */ @property (nonatomic, strong) NSDate *lastDate; +/** The selection mode that the calendar supports i.e single or multiple + + Set this property to `TSQSelectionModeMultiple` or `TSQSelectionModeSingle`; `TSQSelectionModeMultiple` will allow mutliple selection of dates. + Defaults to `TSQSelectionModeSingle` single selection. + */ +@property (nonatomic, assign) TSQSelectionMode selectionMode; + /** The currently-selected date on the calendar. Set this property to any `NSDate`; `TSQCalendarView` will only look at the month, day, and year. - You can read and write this property; the delegate method `calendarView:didSelectDate:` will be called both when a new date is selected from the UI and when this method is called manually. + You can read and write this property. If `calendarView` is configured to `selectionMode` `TSQSelectionModeMultiple` + then this property does nothing and returns nil, use `selectedDates` instead. */ @property (nonatomic, strong) NSDate *selectedDate; +/** The currently-selected dates on the calendar. + + Set this property to an Array of `NSDate` objects; `TSQCalendarView` will only look at the month, day, and year. + You can read and write this property. If `calendarView` is configured to `selectionMode` `TSQSelectionModeSingle` + then this property does nothing and returns an empty array, use `selectedDate` instead. + */ +@property (nonatomic, strong) NSArray *selectedDates; + /** @name Calendar Configuration */ /** The calendar type to use when displaying. @@ -71,6 +93,18 @@ */ @property (nonatomic) BOOL pagingEnabled; +/** Whether or not the calendar can be scrolled, useful for fixed calendar views. + + This property is the equivalent to the one defined on `UIScrollView`. + */ +@property (nonatomic) BOOL scrollingEnabled; + +/** Whether or not the calendar bounces, useful for when scrolling is disabled. + + This property is the equivalent to the one defined on `UIScrollView`. + */ +@property (nonatomic) BOOL bounces; + /** The distance from the edges of the view to where the content begins. This property is equivalent to the one defined on `UIScrollView`. @@ -126,9 +160,20 @@ /** Tells the delegate that a particular date was selected. + `selectionMode` must be `TSQSelectionModeSingle` for this method to be called. + @param calendarView The calendar view that is selecting a date. @param date Midnight on the date being selected. */ - (void)calendarView:(TSQCalendarView *)calendarView didSelectDate:(NSDate *)date; +/** Tells the delegate that one of more dates were selected. + + `selectionMode` must be `TSQSelectionModeMultiple` for this method to be called. + + @param calendarView The calendar view that is selecting a date. + @param dates Array of selected dates each date being on Midnight. + */ +- (void)calendarView:(TSQCalendarView *)calendarView didSelectDates:(NSArray *)dates; + @end diff --git a/TimesSquare/TSQCalendarView.m b/TimesSquare/TSQCalendarView.m index 4b73585..603af01 100644 --- a/TimesSquare/TSQCalendarView.m +++ b/TimesSquare/TSQCalendarView.m @@ -47,6 +47,9 @@ - (id)initWithFrame:(CGRect)frame; - (void)_TSQCalendarView_commonInit; { + _selectionMode = TSQSelectionModeSingle; + _selectedDates = @[]; + _tableView = [[UITableView alloc] initWithFrame:self.bounds style:UITableViewStylePlain]; _tableView.dataSource = self; _tableView.delegate = self; @@ -125,8 +128,10 @@ - (void)setLastDate:(NSDate *)lastDate; - (void)setSelectedDate:(NSDate *)newSelectedDate; { + NSAssert(self.selectionMode == TSQSelectionModeSingle, @"`selectionMode` must be set to `TSQSelectionModeSingle` to select a single date"); + // clamp to beginning of its day - NSDate *startOfDay = [self clampDate:newSelectedDate toComponents:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit]; + NSDate *startOfDay = [self clampDate:newSelectedDate]; if ([self.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)] && ![self.delegate calendarView:self shouldSelectDate:startOfDay]) { return; @@ -134,6 +139,7 @@ - (void)setSelectedDate:(NSDate *)newSelectedDate; [[self cellForRowAtDate:_selectedDate] selectColumnForDate:nil]; [[self cellForRowAtDate:startOfDay] selectColumnForDate:startOfDay]; + NSIndexPath *newIndexPath = [self indexPathForRowAtDate:startOfDay]; CGRect newIndexPathRect = [self.tableView rectForRowAtIndexPath:newIndexPath]; CGRect scrollBounds = self.tableView.bounds; @@ -156,12 +162,47 @@ - (void)setSelectedDate:(NSDate *)newSelectedDate; } } +- (void)setSelectedDates:(NSArray *)selectedDates +{ + NSAssert(self.selectionMode == TSQSelectionModeMultiple, @"`selectionMode` must be set to `TSQSelectionModeMultiple` to select multiple dates"); + + // clamp all dates + NSMutableArray *clampedDates = [@[] mutableCopy]; + [selectedDates enumerateObjectsUsingBlock:^(NSDate *obj, NSUInteger idx, BOOL *stop) { + [clampedDates addObject:[self clampDate:obj]]; + }]; + + for (NSDate *date in _selectedDates) { + [[self cellForRowAtDate:date] selectColumnForDate:date]; + } + + for (NSDate *date in clampedDates) { + [[self cellForRowAtDate:date] selectColumnForDate:date]; + } + + _selectedDates = clampedDates; + + if ([self.delegate respondsToSelector:@selector(calendarView:didSelectDates:)]) { + [self.delegate calendarView:self didSelectDates:clampedDates]; + } +} + - (void)scrollToDate:(NSDate *)date animated:(BOOL)animated { NSInteger section = [self sectionForDate:date]; [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:animated]; } +- (void)setScrollingEnabled:(BOOL)scrollingEnabled +{ + self.tableView.scrollEnabled = scrollingEnabled; +} + +- (void)setBounces:(BOOL)bounces +{ + self.tableView.bounces = bounces; +} + - (TSQCalendarMonthHeaderCell *)makeHeaderCellWithIdentifier:(NSString *)identifier; { TSQCalendarMonthHeaderCell *cell = [[[self headerCellClass] alloc] initWithCalendar:self.calendar reuseIdentifier:identifier]; @@ -204,6 +245,21 @@ - (NSIndexPath *)indexPathForRowAtDate:(NSDate *)date; return [NSIndexPath indexPathForRow:(self.pinsHeaderToTop ? 0 : 1) + targetWeek - firstWeek inSection:section]; } + +- (NSDate *)clampDate:(NSDate *)date +{ + NSDate *startOfDay = [self clampDate:date toComponents:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit]; + return startOfDay; +} + + +- (NSDate *)clampDate:(NSDate *)date toComponents:(NSUInteger)unitFlags +{ + NSDateComponents *components = [self.calendar components:unitFlags fromDate:date]; + return [self.calendar dateFromComponents:components]; +} + + #pragma mark UIView - (void)layoutSubviews; @@ -281,7 +337,14 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)ce dateComponents.day = 1 - ordinalityOfFirstDay; dateComponents.week = indexPath.row - (self.pinsHeaderToTop ? 0 : 1); [(TSQCalendarRowCell *)cell setBeginningDate:[self.calendar dateByAddingComponents:dateComponents toDate:firstOfMonth options:0]]; - [(TSQCalendarRowCell *)cell selectColumnForDate:self.selectedDate]; + + if (self.selectionMode == TSQSelectionModeMultiple) { + for (NSDate *date in self.selectedDates) { + [(TSQCalendarRowCell *)cell selectColumnForDate:date]; + } + } else { + [(TSQCalendarRowCell *)cell selectColumnForDate:self.selectedDate]; + } BOOL isBottomRow = (indexPath.row == [self tableView:tableView numberOfRowsInSection:indexPath.section] - (self.pinsHeaderToTop ? 0 : 1)); [(TSQCalendarRowCell *)cell setBottomRow:isBottomRow]; @@ -317,10 +380,4 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView; } } -- (NSDate *)clampDate:(NSDate *)date toComponents:(NSUInteger)unitFlags -{ - NSDateComponents *components = [self.calendar components:unitFlags fromDate:date]; - return [self.calendar dateFromComponents:components]; -} - @end diff --git a/TimesSquareTestApp/TSQTAAppDelegate.m b/TimesSquareTestApp/TSQTAAppDelegate.m index e1972e8..50f58e3 100644 --- a/TimesSquareTestApp/TSQTAAppDelegate.m +++ b/TimesSquareTestApp/TSQTAAppDelegate.m @@ -20,6 +20,14 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( gregorian.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; gregorian.calendar.locale = [NSLocale currentLocale]; + TSQTAViewController *gregorianMultiple = [[TSQTAViewController alloc] init]; + gregorianMultiple.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; + gregorianMultiple.calendar.locale = [NSLocale currentLocale]; + gregorianMultiple.multipleSelection = YES; + + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:gregorianMultiple]; + navController.title = @"multiple"; + TSQTAViewController *hebrew = [[TSQTAViewController alloc] init]; hebrew.calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar]; hebrew.calendar.locale = [NSLocale currentLocale]; @@ -37,7 +45,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( persian.calendar.locale = [NSLocale currentLocale]; UITabBarController *tabController = [[UITabBarController alloc] init]; - tabController.viewControllers = @[gregorian, hebrew, islamic, indian, persian]; + tabController.viewControllers = @[gregorian, navController, hebrew, islamic, indian, persian]; self.window.rootViewController = tabController; [self.window makeKeyAndVisible]; diff --git a/TimesSquareTestApp/TSQTAViewController.h b/TimesSquareTestApp/TSQTAViewController.h index be8d30e..ed10e10 100644 --- a/TimesSquareTestApp/TSQTAViewController.h +++ b/TimesSquareTestApp/TSQTAViewController.h @@ -12,5 +12,6 @@ @interface TSQTAViewController : UIViewController @property (nonatomic, strong) NSCalendar *calendar; +@property (nonatomic, assign) BOOL multipleSelection; @end diff --git a/TimesSquareTestApp/TSQTAViewController.m b/TimesSquareTestApp/TSQTAViewController.m index 170e8bc..513289f 100644 --- a/TimesSquareTestApp/TSQTAViewController.m +++ b/TimesSquareTestApp/TSQTAViewController.m @@ -12,9 +12,12 @@ #import -@interface TSQTAViewController () +@interface TSQTAViewController () @property (nonatomic, retain) NSTimer *timer; +@property (nonatomic, strong) NSDate *firstDateInCurrentMonth; +@property (nonatomic, strong) NSDate *lastDateInCurrentMonth; +@property (nonatomic, strong) TSQCalendarView *calendarView; @end @@ -30,17 +33,67 @@ @implementation TSQTAViewController - (void)loadView; { - TSQCalendarView *calendarView = [[TSQCalendarView alloc] init]; - calendarView.calendar = self.calendar; - calendarView.rowCellClass = [TSQTACalendarRowCell class]; - calendarView.firstDate = [NSDate dateWithTimeIntervalSinceNow:-60 * 60 * 24 * 365 * 1]; - calendarView.lastDate = [NSDate dateWithTimeIntervalSinceNow:60 * 60 * 24 * 365 * 5]; - calendarView.backgroundColor = [UIColor colorWithRed:0.84f green:0.85f blue:0.86f alpha:1.0f]; - calendarView.pagingEnabled = YES; + self.firstDateInCurrentMonth = [self firstDateInMonthOfReferenceDate:[NSDate date]]; + self.lastDateInCurrentMonth = [self lastDateInMonthOfReferenceDate:[NSDate date]]; + + self.calendarView = [[TSQCalendarView alloc] init]; + self.calendarView.calendar = self.calendar; + self.calendarView.rowCellClass = [TSQTACalendarRowCell class]; + self.calendarView.firstDate = self.firstDateInCurrentMonth; +// self.calendarView.lastDate = self.lastDateInCurrentMonth; + self.calendarView.backgroundColor = [UIColor colorWithRed:0.84f green:0.85f blue:0.86f alpha:1.0f]; + self.calendarView.pagingEnabled = YES; + self.calendarView.selectionMode = self.multipleSelection ? TSQSelectionModeMultiple : TSQSelectionModeSingle; + self.calendarView.delegate = self; CGFloat onePixel = 1.0f / [UIScreen mainScreen].scale; - calendarView.contentInset = UIEdgeInsetsMake(0.0f, onePixel, 0.0f, onePixel); + self.calendarView.contentInset = UIEdgeInsetsMake(0.0f, onePixel, 0.0f, onePixel); + + UIBarButtonItem *selectAllButton = [[UIBarButtonItem alloc] initWithTitle:@"Select All" style:UIBarButtonItemStyleBordered target:self action:@selector(selectAllButtonWasTapped:)]; + self.navigationItem.rightBarButtonItem = selectAllButton; + UIBarButtonItem *clearAllButton = [[UIBarButtonItem alloc] initWithTitle:@"Clear All" style:UIBarButtonItemStyleBordered target:self action:@selector(clearAllButtonWasTapped:)]; + self.navigationItem.leftBarButtonItem = clearAllButton; + + self.view = self.calendarView; +} + +- (NSDate *)firstDateInMonthOfReferenceDate:(NSDate *)date +{ + NSDateComponents *dateComponents = [self.calendar components:NSYearCalendarUnit|NSMonthCalendarUnit fromDate:date]; + return [self.calendar dateFromComponents:dateComponents]; +} + - self.view = calendarView; +- (NSDate *)lastDateInMonthOfReferenceDate:(NSDate *)date +{ + NSDateComponents *dateComponents = [self.calendar components:NSYearCalendarUnit|NSMonthCalendarUnit fromDate:date]; + + // set last of month + dateComponents.month += 1; + dateComponents.day = 0; + + return [self.calendar dateFromComponents:dateComponents]; +} + +- (void)selectAllButtonWasTapped:(UIBarButtonItem *)sender +{ + static NSDateComponents *oneDay; + if (oneDay == nil) { + oneDay = [NSDateComponents new]; + oneDay.day = 1; + } + + NSMutableArray *tmp = [@[] mutableCopy]; + NSDate *date = self.firstDateInCurrentMonth; + while ([date compare:self.lastDateInCurrentMonth] != NSOrderedDescending) { + [tmp addObject:date]; + date = [self.calendar dateByAddingComponents:oneDay toDate:date options:0]; + } + self.calendarView.selectedDates = tmp; +} + +- (void)clearAllButtonWasTapped:(UIBarButtonItem *)sender +{ + self.calendarView.selectedDates = nil; } - (void)setCalendar:(NSCalendar *)calendar; @@ -81,4 +134,25 @@ - (void)scroll; atTop = !atTop; } + +# pragma mark - +# pragma mark TSQCalendarViewDelegate + +- (void)calendarView:(TSQCalendarView *)calendarView didSelectDate:(NSDate *)date +{ + NSLog(@"Did Select Date: %@", date); +} + + +- (void)calendarView:(TSQCalendarView *)calendarView didSelectDates:(NSArray *)dates +{ + NSLog(@"Did Select Dates: %@", dates); +} + + +- (BOOL)calendarView:(TSQCalendarView *)calendarView shouldSelectDate:(NSDate *)date +{ + return YES; +} + @end diff --git a/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj b/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj index fa1eb4f..c0a0d37 100644 --- a/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj +++ b/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 7E96734B1A6E6BAE00B6CAFC /* Reveal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E96734A1A6E6BAE00B6CAFC /* Reveal.framework */; }; A885A9771694FCDD00CA6E1B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A885A9761694FCDD00CA6E1B /* UIKit.framework */; }; A885A9791694FCDD00CA6E1B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A885A9781694FCDD00CA6E1B /* Foundation.framework */; }; A885A97B1694FCDD00CA6E1B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A885A97A1694FCDD00CA6E1B /* CoreGraphics.framework */; }; @@ -61,6 +62,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7E96734A1A6E6BAE00B6CAFC /* Reveal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reveal.framework; path = "../../../../../../../../../Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/Reveal.framework"; sourceTree = ""; }; A885A9541694FC8A00CA6E1B /* CalendarPreviousMonth.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = CalendarPreviousMonth.png; sourceTree = ""; }; A885A9551694FC8A00CA6E1B /* CalendarPreviousMonth@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "CalendarPreviousMonth@2x.png"; sourceTree = ""; }; A885A9561694FC8A00CA6E1B /* CalendarRow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = CalendarRow.png; sourceTree = ""; }; @@ -106,6 +108,7 @@ A885A9771694FCDD00CA6E1B /* UIKit.framework in Frameworks */, A885A9791694FCDD00CA6E1B /* Foundation.framework in Frameworks */, A885A97B1694FCDD00CA6E1B /* CoreGraphics.framework in Frameworks */, + 7E96734B1A6E6BAE00B6CAFC /* Reveal.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -173,6 +176,7 @@ A885A96E1694FCC000CA6E1B /* Frameworks */ = { isa = PBXGroup; children = ( + 7E96734A1A6E6BAE00B6CAFC /* Reveal.framework */, A885A9A91694FD3900CA6E1B /* TimesSquare.xcodeproj */, A885A9761694FCDD00CA6E1B /* UIKit.framework */, A885A9781694FCDD00CA6E1B /* Foundation.framework */, @@ -257,7 +261,7 @@ name = TimesSquareTestAppTests; productName = TimesSquareTestAppTests; productReference = A885A9931694FCDD00CA6E1B /* TimesSquareTestAppTests.octest */; - productType = "com.apple.product-type.bundle"; + productType = "com.apple.product-type.bundle.ocunit-test"; }; /* End PBXNativeTarget section */ @@ -432,6 +436,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", + "$(SYSTEM_APPS_DIR)/Reveal.app/Contents/SharedSupport/iOS-Libraries", ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -449,6 +454,12 @@ INFOPLIST_FILE = "$(SRCROOT)/TimesSquareTestApp-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-ObjC", + "-lz", + "-framework", + Reveal, + ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; WRAPPER_EXTENSION = app; @@ -468,6 +479,7 @@ FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "\"$(SYSTEM_APPS_DIR)/Xcode.app/Contents/Developer/Library/Frameworks\"", + "$(SYSTEM_APPS_DIR)/Reveal.app/Contents/SharedSupport/iOS-Libraries", ); GCC_C_LANGUAGE_STANDARD = gnu99; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -478,6 +490,12 @@ INFOPLIST_FILE = "$(SRCROOT)/TimesSquareTestApp-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + OTHER_LDFLAGS = ( + "-ObjC", + "-lz", + "-framework", + Reveal, + ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; From d3f6a3efe5da87ceb281ec62725a89d54adf50c4 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 20 Jan 2015 15:36:07 +0000 Subject: [PATCH 2/7] Removed Reveal.framework which was included in error. --- .../TimesSquareTestApp.xcodeproj/project.pbxproj | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj b/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj index c0a0d37..428d5f9 100644 --- a/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj +++ b/TimesSquareTestApp/TimesSquareTestApp.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 7E96734B1A6E6BAE00B6CAFC /* Reveal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E96734A1A6E6BAE00B6CAFC /* Reveal.framework */; }; A885A9771694FCDD00CA6E1B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A885A9761694FCDD00CA6E1B /* UIKit.framework */; }; A885A9791694FCDD00CA6E1B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A885A9781694FCDD00CA6E1B /* Foundation.framework */; }; A885A97B1694FCDD00CA6E1B /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A885A97A1694FCDD00CA6E1B /* CoreGraphics.framework */; }; @@ -108,7 +107,6 @@ A885A9771694FCDD00CA6E1B /* UIKit.framework in Frameworks */, A885A9791694FCDD00CA6E1B /* Foundation.framework in Frameworks */, A885A97B1694FCDD00CA6E1B /* CoreGraphics.framework in Frameworks */, - 7E96734B1A6E6BAE00B6CAFC /* Reveal.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -454,12 +452,6 @@ INFOPLIST_FILE = "$(SRCROOT)/TimesSquareTestApp-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; ONLY_ACTIVE_ARCH = YES; - OTHER_LDFLAGS = ( - "-ObjC", - "-lz", - "-framework", - Reveal, - ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; WRAPPER_EXTENSION = app; @@ -490,12 +482,6 @@ INFOPLIST_FILE = "$(SRCROOT)/TimesSquareTestApp-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 5.0; OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; - OTHER_LDFLAGS = ( - "-ObjC", - "-lz", - "-framework", - Reveal, - ); PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; From 939534ef503831db9fca33e7aacd7bc7148c15c4 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 20 Jan 2015 15:39:42 +0000 Subject: [PATCH 3/7] Added larger date range on example view controller. --- TimesSquareTestApp/TSQTAViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TimesSquareTestApp/TSQTAViewController.m b/TimesSquareTestApp/TSQTAViewController.m index 513289f..6de1c33 100644 --- a/TimesSquareTestApp/TSQTAViewController.m +++ b/TimesSquareTestApp/TSQTAViewController.m @@ -34,13 +34,13 @@ @implementation TSQTAViewController - (void)loadView; { self.firstDateInCurrentMonth = [self firstDateInMonthOfReferenceDate:[NSDate date]]; - self.lastDateInCurrentMonth = [self lastDateInMonthOfReferenceDate:[NSDate date]]; + self.lastDateInCurrentMonth = [self.firstDateInCurrentMonth dateByAddingTimeInterval:60 * 60 * 24 * 365]; self.calendarView = [[TSQCalendarView alloc] init]; self.calendarView.calendar = self.calendar; self.calendarView.rowCellClass = [TSQTACalendarRowCell class]; self.calendarView.firstDate = self.firstDateInCurrentMonth; -// self.calendarView.lastDate = self.lastDateInCurrentMonth; + self.calendarView.lastDate = self.lastDateInCurrentMonth; self.calendarView.backgroundColor = [UIColor colorWithRed:0.84f green:0.85f blue:0.86f alpha:1.0f]; self.calendarView.pagingEnabled = YES; self.calendarView.selectionMode = self.multipleSelection ? TSQSelectionModeMultiple : TSQSelectionModeSingle; From 0d95196b01367fa223b6c7f6eace96acec81fad8 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Thu, 22 Jan 2015 12:11:41 +0000 Subject: [PATCH 4/7] Added support to disable dates before a specified date. --- TimesSquare/TSQCalendarRowCell.m | 2 +- TimesSquare/TSQCalendarView.h | 8 ++++++++ TimesSquare/TSQCalendarView.m | 5 +++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/TimesSquare/TSQCalendarRowCell.m b/TimesSquare/TSQCalendarRowCell.m index 5653527..3438c15 100644 --- a/TimesSquare/TSQCalendarRowCell.m +++ b/TimesSquare/TSQCalendarRowCell.m @@ -169,7 +169,7 @@ - (void)setBeginningDate:(NSDate *)date; [self.notThisMonthButtons[index] setHidden:YES]; NSInteger thisDayMonth = thisDateComponents.month; - if (self.monthOfBeginningDate != thisDayMonth) { + if (self.monthOfBeginningDate != thisDayMonth || [self.calendarView.firstSelectableDate compare:date] == NSOrderedDescending) { [self.notThisMonthButtons[index] setHidden:NO]; } else { diff --git a/TimesSquare/TSQCalendarView.h b/TimesSquare/TSQCalendarView.h index 6117fbd..7bc25ae 100644 --- a/TimesSquare/TSQCalendarView.h +++ b/TimesSquare/TSQCalendarView.h @@ -41,6 +41,14 @@ typedef NS_ENUM(NSInteger, TSQSelectionMode) { */ @property (nonatomic, strong) NSDate *lastDate; +/** The first date that can be selected in the calendar view. + + Set this property to any `NSDate`; `TSQCalendarView` will disable interaction of cells + for all dates before the `firstSelectableDate`, If `firstSelectableDate` is before or after + the bounds of `firstDate` and `lastDate` then this value will be ignored. + */ +@property (nonatomic, strong) NSDate *firstSelectableDate; + /** The selection mode that the calendar supports i.e single or multiple Set this property to `TSQSelectionModeMultiple` or `TSQSelectionModeSingle`; `TSQSelectionModeMultiple` will allow mutliple selection of dates. diff --git a/TimesSquare/TSQCalendarView.m b/TimesSquare/TSQCalendarView.m index 603af01..b7866c0 100644 --- a/TimesSquare/TSQCalendarView.m +++ b/TimesSquare/TSQCalendarView.m @@ -126,6 +126,11 @@ - (void)setLastDate:(NSDate *)lastDate; _lastDate = [self.calendar dateByAddingComponents:offsetComponents toDate:firstOfMonth options:0]; } +- (void)setFirstSelectableDate:(NSDate *)firstSelectableDate +{ + _firstSelectableDate = [self clampDate:firstSelectableDate toComponents:NSMonthCalendarUnit|NSYearCalendarUnit|NSDayCalendarUnit]; +} + - (void)setSelectedDate:(NSDate *)newSelectedDate; { NSAssert(self.selectionMode == TSQSelectionModeSingle, @"`selectionMode` must be set to `TSQSelectionModeSingle` to select a single date"); From 9049f3ffa851e54842aa32e019ef0670e01c4d91 Mon Sep 17 00:00:00 2001 From: Chris Leversuch Date: Tue, 14 Apr 2015 10:37:56 +0100 Subject: [PATCH 5/7] Fix build error - use fabs instead of fabsf --- TimesSquare/TSQCalendarCell.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TimesSquare/TSQCalendarCell.m b/TimesSquare/TSQCalendarCell.m index 99276c9..b49489c 100644 --- a/TimesSquare/TSQCalendarCell.m +++ b/TimesSquare/TSQCalendarCell.m @@ -97,7 +97,7 @@ - (void)layoutSubviews; CGFloat extraSpace = (CGRectGetWidth(insetRect) - (self.daysInWeek - 1) * self.columnSpacing) - (increment * self.daysInWeek); // Divide the extra space out over the outer columns in increments of the column spacing - NSInteger columnsWithExtraSpace = (NSInteger)fabsf(extraSpace / self.columnSpacing); + NSInteger columnsWithExtraSpace = (NSInteger)fabs(extraSpace / self.columnSpacing); NSInteger columnsOnLeftWithExtraSpace = columnsWithExtraSpace / 2; NSInteger columnsOnRightWithExtraSpace = columnsWithExtraSpace - columnsOnLeftWithExtraSpace; From 9ad85e67bbca81d7d92480f3ecc647a33bae1d22 Mon Sep 17 00:00:00 2001 From: Cameron Cooke Date: Tue, 2 Jun 2015 14:38:19 +0100 Subject: [PATCH 6/7] Updated project settings. --- TimesSquare.xcodeproj/project.pbxproj | 3 ++- .../xcschemes/TimesSquare Documentation.xcscheme | 11 ++++++++++- .../xcshareddata/xcschemes/TimesSquare.xcscheme | 11 ++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/TimesSquare.xcodeproj/project.pbxproj b/TimesSquare.xcodeproj/project.pbxproj index ad6ab3e..feeac6b 100644 --- a/TimesSquare.xcodeproj/project.pbxproj +++ b/TimesSquare.xcodeproj/project.pbxproj @@ -175,7 +175,7 @@ A806805216700FD70071C71E /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0630; ORGANIZATIONNAME = Square; }; buildConfigurationList = A806805516700FD70071C71E /* Build configuration list for PBXProject "TimesSquare" */; @@ -234,6 +234,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 5.0; + ONLY_ACTIVE_ARCH = YES; PUBLIC_HEADERS_FOLDER_PATH = "include/$(PRODUCT_NAME)"; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; diff --git a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme index a124484..d4c657e 100644 --- a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme +++ b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme @@ -1,6 +1,6 @@ + + + + diff --git a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme index 4446f9f..cb5a318 100644 --- a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme +++ b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare.xcscheme @@ -1,6 +1,6 @@ + + + + From 54bd424d645727a5d220b70916a9f07ac8e9deca Mon Sep 17 00:00:00 2001 From: Chris Leversuch Date: Thu, 19 Nov 2015 14:17:16 +0000 Subject: [PATCH 7/7] Update project settings --- .../xcschemes/TimesSquare Documentation.xcscheme | 13 ++++++++----- .../xcshareddata/xcschemes/TimesSquare.xcscheme | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme index d4c657e..362c5eb 100644 --- a/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme +++ b/TimesSquare.xcodeproj/xcshareddata/xcschemes/TimesSquare Documentation.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> + + + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -39,15 +39,18 @@ + +