diff --git a/TimesSquare.xcodeproj/project.pbxproj b/TimesSquare.xcodeproj/project.pbxproj index bb0983a..aa6cae0 100644 --- a/TimesSquare.xcodeproj/project.pbxproj +++ b/TimesSquare.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ EFD8DE6F167AF78600F87FBE /* TSQCalendarMonthHeaderCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A8068088167010030071C71E /* TSQCalendarMonthHeaderCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; EFD8DE70167AF78C00F87FBE /* TSQCalendarRowCell.h in Headers */ = {isa = PBXBuildFile; fileRef = A806808A167010030071C71E /* TSQCalendarRowCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; EFD8DE71167AF79000F87FBE /* TSQCalendarView.h in Headers */ = {isa = PBXBuildFile; fileRef = A806808C167010030071C71E /* TSQCalendarView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F6C9C449173D29C3004EA826 /* TSQCalendarRowButton.h in Headers */ = {isa = PBXBuildFile; fileRef = F6C9C447173D29C3004EA826 /* TSQCalendarRowButton.h */; }; + F6C9C44A173D29C3004EA826 /* TSQCalendarRowButton.m in Sources */ = {isa = PBXBuildFile; fileRef = F6C9C448173D29C3004EA826 /* TSQCalendarRowButton.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -48,6 +50,8 @@ A806808C167010030071C71E /* TSQCalendarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarView.h; sourceTree = ""; }; A806808D167010030071C71E /* TSQCalendarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarView.m; sourceTree = ""; }; A806809E167012980071C71E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + F6C9C447173D29C3004EA826 /* TSQCalendarRowButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSQCalendarRowButton.h; sourceTree = ""; }; + F6C9C448173D29C3004EA826 /* TSQCalendarRowButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSQCalendarRowButton.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -99,6 +103,8 @@ A8068087167010030071C71E /* TSQCalendarCell.m */, A8068088167010030071C71E /* TSQCalendarMonthHeaderCell.h */, A8068089167010030071C71E /* TSQCalendarMonthHeaderCell.m */, + F6C9C447173D29C3004EA826 /* TSQCalendarRowButton.h */, + F6C9C448173D29C3004EA826 /* TSQCalendarRowButton.m */, A806808A167010030071C71E /* TSQCalendarRowCell.h */, A806808B167010030071C71E /* TSQCalendarRowCell.m */, A806808C167010030071C71E /* TSQCalendarView.h */, @@ -128,6 +134,7 @@ EFD8DE6F167AF78600F87FBE /* TSQCalendarMonthHeaderCell.h in Headers */, EFD8DE70167AF78C00F87FBE /* TSQCalendarRowCell.h in Headers */, EFD8DE71167AF79000F87FBE /* TSQCalendarView.h in Headers */, + F6C9C449173D29C3004EA826 /* TSQCalendarRowButton.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -205,6 +212,7 @@ A8068090167010030071C71E /* TSQCalendarMonthHeaderCell.m in Sources */, A8068092167010030071C71E /* TSQCalendarRowCell.m in Sources */, A8068094167010030071C71E /* TSQCalendarView.m in Sources */, + F6C9C44A173D29C3004EA826 /* TSQCalendarRowButton.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TimesSquare/TSQCalendarRowButton.h b/TimesSquare/TSQCalendarRowButton.h new file mode 100644 index 0000000..a15d961 --- /dev/null +++ b/TimesSquare/TSQCalendarRowButton.h @@ -0,0 +1,34 @@ +// +// TSQCalendarButton.h +// TimesSquare +// +// Created by Simon Booth on 10/05/2013. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + + +#import +#import "TSQCalendarRowCell.h" + +/** The `TSQCalendarRowButton` class is a button that represents single day in the calendar. + + The button contains an additional label which is used to display an event marker. + */ +@interface TSQCalendarRowButton : UIButton + +/** A label used to display an event marker + + The marker is shown using the bullet character '•' + + */ +@property (nonatomic, strong, readonly) UILabel *subtitleLabel; + +/** Configures the button according to the given row's properties + + The button is set up using the text color and shadow offset of the row + + */ +- (void)configureWithRowCell:(TSQCalendarRowCell *)rowCell; + +@end diff --git a/TimesSquare/TSQCalendarRowButton.m b/TimesSquare/TSQCalendarRowButton.m new file mode 100644 index 0000000..8c4b2ad --- /dev/null +++ b/TimesSquare/TSQCalendarRowButton.m @@ -0,0 +1,82 @@ +// +// TSQCalendarButton.m +// TimesSquare +// +// Created by Simon Booth on 10/05/2013. +// Licensed to Square, Inc. under one or more contributor license agreements. +// See the LICENSE file distributed with this work for the terms under +// which Square, Inc. licenses this file to you. + +#import "TSQCalendarRowButton.h" + + +@implementation TSQCalendarRowButton + +- (id)initWithFrame:(CGRect)frame; +{ + self = [super initWithFrame:frame]; + if (self) { + self.titleLabel.font = [UIFont boldSystemFontOfSize:19.f]; + self.adjustsImageWhenDisabled = NO; + [self setTitleShadowColor:[UIColor whiteColor] forState:UIControlStateNormal]; + + _subtitleLabel = [[UILabel alloc] init]; + _subtitleLabel.backgroundColor = [UIColor clearColor]; + _subtitleLabel.font = self.titleLabel.font; + _subtitleLabel.textAlignment = UITextAlignmentCenter; + [self addSubview:_subtitleLabel]; + + [self updateSubtitleLabel]; + } + return self; +} + +- (void)configureWithRowCell:(TSQCalendarRowCell *)rowCell; +{ + [self setTitleColor:rowCell.textColor forState:UIControlStateNormal]; + self.titleLabel.shadowOffset = rowCell.shadowOffset; + [self updateSubtitleLabel]; +} + +- (void)layoutSubviews; +{ + [super layoutSubviews]; + + CGRect subtitleFrame = self.bounds; + subtitleFrame.origin.y = subtitleFrame.size.height - 15; + subtitleFrame.size.height = 15; + self.subtitleLabel.frame = subtitleFrame; +} + +- (void)updateSubtitleLabel; +{ + self.subtitleLabel.textColor = self.currentTitleColor; + self.subtitleLabel.shadowColor = self.currentTitleShadowColor; + self.subtitleLabel.shadowOffset = self.titleLabel.shadowOffset; +} + +- (void)setTitleShadowColor:(UIColor *)color forState:(UIControlState)state; +{ + [super setTitleShadowColor:color forState:state]; + [self updateSubtitleLabel]; +} + +- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state; +{ + [super setTitleColor:color forState:state]; + [self updateSubtitleLabel]; +} + +- (void)setHighlighted:(BOOL)highlighted; +{ + [super setHighlighted:highlighted]; + [self updateSubtitleLabel]; +} + +- (void)setSelected:(BOOL)selected; +{ + [super setSelected:selected]; + [self updateSubtitleLabel]; +} + +@end diff --git a/TimesSquare/TSQCalendarRowCell.h b/TimesSquare/TSQCalendarRowCell.h index 0fad589..63add92 100644 --- a/TimesSquare/TSQCalendarRowCell.h +++ b/TimesSquare/TSQCalendarRowCell.h @@ -65,4 +65,12 @@ */ - (void)selectColumnForDate:(NSDate *)date; +/** @name Button configuration */ + +/** The button class to use for day buttons. + + The class should be a subclass of `TSQCalendarRowButton` or at least implement all of its methods. + */ +@property (nonatomic, strong) Class rowButtonClass; + @end diff --git a/TimesSquare/TSQCalendarRowCell.m b/TimesSquare/TSQCalendarRowCell.m index 312a7f2..0352087 100644 --- a/TimesSquare/TSQCalendarRowCell.m +++ b/TimesSquare/TSQCalendarRowCell.m @@ -9,14 +9,14 @@ #import "TSQCalendarRowCell.h" #import "TSQCalendarView.h" - +#import "TSQCalendarRowButton.h" @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) TSQCalendarRowButton *todayButton; +@property (nonatomic, strong) TSQCalendarRowButton *selectedButton; @property (nonatomic, assign) NSInteger indexOfTodayButton; @property (nonatomic, assign) NSInteger indexOfSelectedButton; @@ -42,24 +42,23 @@ - (id)initWithCalendar:(NSCalendar *)calendar reuseIdentifier:(NSString *)reuseI return self; } -- (void)configureButton:(UIButton *)button; +- (Class)rowButtonClass; { - button.titleLabel.font = [UIFont boldSystemFontOfSize:19.f]; - button.titleLabel.shadowOffset = self.shadowOffset; - button.adjustsImageWhenDisabled = NO; - [button setTitleColor:self.textColor forState:UIControlStateNormal]; - [button setTitleShadowColor:[UIColor whiteColor] forState:UIControlStateNormal]; + if (!_rowButtonClass) { + self.rowButtonClass = [TSQCalendarRowButton class]; + } + return _rowButtonClass; } - (void)createDayButtons; { NSMutableArray *dayButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; for (NSUInteger index = 0; index < self.daysInWeek; index++) { - UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + TSQCalendarRowButton *button = [[self.rowButtonClass alloc] initWithFrame:self.contentView.bounds]; [button addTarget:self action:@selector(dateButtonPressed:) forControlEvents:UIControlEventTouchDown]; [dayButtons addObject:button]; [self.contentView addSubview:button]; - [self configureButton:button]; + [button configureWithRowCell:self]; [button setTitleColor:[self.textColor colorWithAlphaComponent:0.5f] forState:UIControlStateDisabled]; } self.dayButtons = dayButtons; @@ -69,10 +68,10 @@ - (void)createNotThisMonthButtons; { NSMutableArray *notThisMonthButtons = [NSMutableArray arrayWithCapacity:self.daysInWeek]; for (NSUInteger index = 0; index < self.daysInWeek; index++) { - UIButton *button = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + TSQCalendarRowButton *button = [[self.rowButtonClass alloc] initWithFrame:self.contentView.bounds]; [notThisMonthButtons addObject:button]; [self.contentView addSubview:button]; - [self configureButton:button]; + [button configureWithRowCell:self]; button.enabled = NO; UIColor *backgroundPattern = [UIColor colorWithPatternImage:[self notThisMonthBackgroundImage]]; @@ -84,9 +83,9 @@ - (void)createNotThisMonthButtons; - (void)createTodayButton; { - self.todayButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + self.todayButton = [[self.rowButtonClass alloc] initWithFrame:self.contentView.bounds]; [self.contentView addSubview:self.todayButton]; - [self configureButton:self.todayButton]; + [self.todayButton configureWithRowCell:self]; [self.todayButton addTarget:self action:@selector(todayButtonPressed:) forControlEvents:UIControlEventTouchDown]; [self.todayButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; @@ -98,9 +97,9 @@ - (void)createTodayButton; - (void)createSelectedButton; { - self.selectedButton = [[UIButton alloc] initWithFrame:self.contentView.bounds]; + self.selectedButton = [[self.rowButtonClass alloc] initWithFrame:self.contentView.bounds]; [self.contentView addSubview:self.selectedButton]; - [self configureButton:self.selectedButton]; + [self.selectedButton configureWithRowCell:self]; [self.selectedButton setAccessibilityTraits:UIAccessibilityTraitSelected|self.selectedButton.accessibilityTraits]; @@ -127,11 +126,14 @@ - (void)setBeginningDate:(NSDate *)date; for (NSUInteger index = 0; index < self.daysInWeek; index++) { NSString *title = [self.dayFormatter stringFromDate:date]; + NSString *subTitle = [self.calendarView shouldDisplayEventMarkerForDate:date] ? @"•" : nil; NSString *accessibilityLabel = [self.accessibilityFormatter stringFromDate:date]; [self.dayButtons[index] setTitle:title forState:UIControlStateNormal]; [self.dayButtons[index] setAccessibilityLabel:accessibilityLabel]; + [[self.dayButtons[index] subtitleLabel] setText:subTitle]; [self.notThisMonthButtons[index] setTitle:title forState:UIControlStateNormal]; [self.notThisMonthButtons[index] setAccessibilityLabel:accessibilityLabel]; + [[self.notThisMonthButtons[index] subtitleLabel] setText:subTitle]; NSDateComponents *thisDateComponents = [self.calendar components:NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:date]; @@ -147,9 +149,10 @@ - (void)setBeginningDate:(NSDate *)date; self.todayButton.hidden = NO; [self.todayButton setTitle:title forState:UIControlStateNormal]; [self.todayButton setAccessibilityLabel:accessibilityLabel]; + [self.todayButton.subtitleLabel setText:subTitle]; self.indexOfTodayButton = index; } else { - UIButton *button = self.dayButtons[index]; + TSQCalendarRowButton *button = self.dayButtons[index]; button.enabled = ![self.calendarView.delegate respondsToSelector:@selector(calendarView:shouldSelectDate:)] || [self.calendarView.delegate calendarView:self.calendarView shouldSelectDate:date]; button.hidden = NO; } @@ -209,8 +212,8 @@ - (void)layoutSubviews; - (void)layoutViewsForColumnAtIndex:(NSUInteger)index inRect:(CGRect)rect; { - UIButton *dayButton = self.dayButtons[index]; - UIButton *notThisMonthButton = self.notThisMonthButtons[index]; + TSQCalendarRowButton *dayButton = self.dayButtons[index]; + TSQCalendarRowButton *notThisMonthButton = self.notThisMonthButtons[index]; dayButton.frame = rect; notThisMonthButton.frame = rect; @@ -246,6 +249,7 @@ - (void)selectColumnForDate:(NSDate *)date; self.selectedButton.hidden = NO; [self.selectedButton setTitle:[self.dayButtons[newIndexOfSelectedButton] currentTitle] forState:UIControlStateNormal]; [self.selectedButton setAccessibilityLabel:[self.dayButtons[newIndexOfSelectedButton] accessibilityLabel]]; + [self.selectedButton.subtitleLabel setText:[[self.dayButtons[newIndexOfSelectedButton] subtitleLabel] text]]; } else { self.selectedButton.hidden = YES; } diff --git a/TimesSquare/TSQCalendarView.h b/TimesSquare/TSQCalendarView.h index d6f0dab..a31fbac 100644 --- a/TimesSquare/TSQCalendarView.h +++ b/TimesSquare/TSQCalendarView.h @@ -104,6 +104,15 @@ */ - (void)scrollToDate:(NSDate *)date animated:(BOOL)animated; +/** Whether a particular date should display an event marker + + This method passes straight through to the delegate + + @param date The date being displayed. + @return Whether or not the date should display an event marker. + */ +- (BOOL)shouldDisplayEventMarkerForDate:(NSDate *)date; + @end /** The methods in the `TSQCalendarViewDelegate` protocol allow the adopting delegate to either prevent a day from being selected or respond to it. @@ -131,4 +140,14 @@ */ - (void)calendarView:(TSQCalendarView *)calendarView didSelectDate:(NSDate *)date; +/** @name Displaying event markers */ + +/** Asks the delegate whether a particular date should display an event marker + + @param calendarView The calendar view that is displaying a date. + @param date The date being displayed. + @return Whether or not the date should display an event marker. + */ +- (BOOL)calendarView:(TSQCalendarView *)calendarView shouldDisplayEventMarkerForDate:(NSDate *)date; + @end diff --git a/TimesSquare/TSQCalendarView.m b/TimesSquare/TSQCalendarView.m index 5678a4e..7667990 100644 --- a/TimesSquare/TSQCalendarView.m +++ b/TimesSquare/TSQCalendarView.m @@ -162,6 +162,15 @@ - (void)scrollToDate:(NSDate *)date animated:(BOOL)animated [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section] atScrollPosition:UITableViewScrollPositionTop animated:animated]; } +- (BOOL)shouldDisplayEventMarkerForDate:(NSDate *)date; +{ + if ([self.delegate respondsToSelector:@selector(calendarView:shouldDisplayEventMarkerForDate:)]) { + return [self.delegate calendarView:self shouldDisplayEventMarkerForDate:date]; + } + + return NO; +} + - (TSQCalendarMonthHeaderCell *)makeHeaderCellWithIdentifier:(NSString *)identifier; { TSQCalendarMonthHeaderCell *cell = [[[self headerCellClass] alloc] initWithCalendar:self.calendar reuseIdentifier:identifier]; diff --git a/TimesSquareTestApp/TSQTAViewController.m b/TimesSquareTestApp/TSQTAViewController.m index 170e8bc..018a0da 100644 --- a/TimesSquareTestApp/TSQTAViewController.m +++ b/TimesSquareTestApp/TSQTAViewController.m @@ -12,7 +12,7 @@ #import -@interface TSQTAViewController () +@interface TSQTAViewController () @property (nonatomic, retain) NSTimer *timer; @@ -39,6 +39,7 @@ - (void)loadView; calendarView.pagingEnabled = YES; CGFloat onePixel = 1.0f / [UIScreen mainScreen].scale; calendarView.contentInset = UIEdgeInsetsMake(0.0f, onePixel, 0.0f, onePixel); + calendarView.delegate = self; self.view = calendarView; } @@ -81,4 +82,12 @@ - (void)scroll; atTop = !atTop; } +- (BOOL)calendarView:(TSQCalendarView *)calendarView shouldDisplayEventMarkerForDate:(NSDate *)date; +{ + NSDateComponents *components = [calendarView.calendar components:NSMonthCalendarUnit|NSDayCalendarUnit fromDate:date]; + + // This gives a nice pattern + return (components.day % 9 == components.month % 9) || (components.day % 11 == components.month % 11); +} + @end