From e61238770d0f8b05e1a701e9b644bc2bd99ab2b2 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Fri, 4 Sep 2015 14:58:58 -0600 Subject: [PATCH] Add API for users to check and be notified of scrolling state It currently involves a lot of work to properly update the calendar asyncronously (as encouraged in the README for long-running update tasks) because you have to avoid reloading the calendar manager while either scroll view is still scrolling. This change adds `scrolling` properties to the menu and horizontal content views so that users can check the current state, as well as adds two more delegate methods called when the content scroll view finishes scrolling. This also occurs when the content scrolling is triggered by menu view scrolling. This means that users can update their data cache when calendarDidLoad*Page is called and then reload the calendar when the new calendarDidFinishScrollingTo*Page is called. --- JTCalendar/JTCalendarDelegate.h | 10 ++ JTCalendar/Managers/JTCalendarScrollManager.h | 3 + JTCalendar/Managers/JTCalendarScrollManager.m | 27 ++++++ JTCalendar/Views/JTCalendarMenuView.h | 2 + JTCalendar/Views/JTCalendarMenuView.m | 28 ++++++ JTCalendar/Views/JTHorizontalCalendarView.h | 2 + JTCalendar/Views/JTHorizontalCalendarView.m | 94 ++++++++++++++++++- 7 files changed, 164 insertions(+), 2 deletions(-) diff --git a/JTCalendar/JTCalendarDelegate.h b/JTCalendar/JTCalendarDelegate.h index 9e1348b..f314d43 100644 --- a/JTCalendar/JTCalendarDelegate.h +++ b/JTCalendar/JTCalendarDelegate.h @@ -61,11 +61,21 @@ */ - (void)calendarDidLoadPreviousPage:(JTCalendarManager *)calendar; +/** + * Indicates the previous page finished scrolling after becoming the current page. + */ +- (void)calendarDidFinishScrollingToPreviousPage:(JTCalendarManager *)calendar; + /*! * Indicate the next page became the current page. */ - (void)calendarDidLoadNextPage:(JTCalendarManager *)calendar; +/** + * Indicates the next page finished scrolling after becoming the current page. + */ +- (void)calendarDidFinishScrollingToNextPage:(JTCalendarManager *)calendar; + /*! * Provide a view conforming to `JTCalendarPage` protocol, used as page for the contentView. * Return an instance of `JTCalendarPageView` by default. diff --git a/JTCalendar/Managers/JTCalendarScrollManager.h b/JTCalendar/Managers/JTCalendarScrollManager.h index b50d812..92df249 100644 --- a/JTCalendar/Managers/JTCalendarScrollManager.h +++ b/JTCalendar/Managers/JTCalendarScrollManager.h @@ -27,5 +27,8 @@ - (void)updateMenuContentOffset:(CGFloat)percentage pageMode:(NSUInteger)pageMode; - (void)updateHorizontalContentOffset:(CGFloat)percentage; +- (void)endDragging; +- (void)endDecelerating; +- (void)endHorizontalScrollingAnimation; @end diff --git a/JTCalendar/Managers/JTCalendarScrollManager.m b/JTCalendar/Managers/JTCalendarScrollManager.m index 293bc25..953018b 100644 --- a/JTCalendar/Managers/JTCalendarScrollManager.m +++ b/JTCalendar/Managers/JTCalendarScrollManager.m @@ -39,4 +39,31 @@ - (void)updateHorizontalContentOffset:(CGFloat)percentage _horizontalContentView.contentOffset = CGPointMake(percentage * _horizontalContentView.contentSize.width, 0); } +- (void)endDragging +{ + if(![_horizontalContentView.delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]){ + return; + } + + [_horizontalContentView.delegate scrollViewDidEndDragging:_horizontalContentView willDecelerate:NO]; +} + +- (void)endDecelerating +{ + if(![_horizontalContentView.delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]){ + return; + } + + [_horizontalContentView.delegate scrollViewDidEndDecelerating:_horizontalContentView]; +} + +- (void)endHorizontalScrollingAnimation +{ + if(![_horizontalContentView.delegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]){ + return; + } + + [_horizontalContentView.delegate scrollViewDidEndScrollingAnimation:_horizontalContentView]; +} + @end diff --git a/JTCalendar/Views/JTCalendarMenuView.h b/JTCalendar/Views/JTCalendarMenuView.h index f6dfb9b..bb520cc 100644 --- a/JTCalendar/Views/JTCalendarMenuView.h +++ b/JTCalendar/Views/JTCalendarMenuView.h @@ -17,6 +17,8 @@ @property (nonatomic, readonly) UIScrollView *scrollView; +@property (nonatomic, assign, readonly) BOOL scrolling; + /*! * Must be call if override the class */ diff --git a/JTCalendar/Views/JTCalendarMenuView.m b/JTCalendar/Views/JTCalendarMenuView.m index 3092f91..772abc8 100644 --- a/JTCalendar/Views/JTCalendarMenuView.m +++ b/JTCalendar/Views/JTCalendarMenuView.m @@ -78,6 +78,15 @@ - (void)layoutSubviews [self resizeViewsIfWidthChanged]; } +#pragma mark - Properties + +- (BOOL)scrolling +{ + return self.scrollView.dragging || self.scrollView.decelerating; +} + +#pragma mark - UIScrollViewDelegate + - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if(_scrollView.contentSize.width <= 0){ @@ -87,6 +96,25 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView [_manager.scrollManager updateHorizontalContentOffset:(_scrollView.contentOffset.x / _scrollView.contentSize.width)]; } +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + if(!decelerate){ + [_manager.scrollManager endDragging]; + } +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + [_manager.scrollManager endDecelerating]; +} + +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView +{ + [_manager.scrollManager endHorizontalScrollingAnimation]; +} + +#pragma mark - + - (void)resizeViewsIfWidthChanged { CGSize size = self.frame.size; diff --git a/JTCalendar/Views/JTHorizontalCalendarView.h b/JTCalendar/Views/JTHorizontalCalendarView.h index faf8339..544cad0 100644 --- a/JTCalendar/Views/JTHorizontalCalendarView.h +++ b/JTCalendar/Views/JTHorizontalCalendarView.h @@ -15,6 +15,8 @@ @property (nonatomic) NSDate *date; +@property (nonatomic, assign, readonly) BOOL scrolling; + /*! * Must be call if override the class */ diff --git a/JTCalendar/Views/JTHorizontalCalendarView.m b/JTCalendar/Views/JTHorizontalCalendarView.m index ad5b20c..0c3e1c4 100644 --- a/JTCalendar/Views/JTHorizontalCalendarView.m +++ b/JTCalendar/Views/JTHorizontalCalendarView.m @@ -16,7 +16,13 @@ typedef NS_ENUM(NSInteger, JTCalendarPageMode) { JTCalendarPageModeCenterRight }; -@interface JTHorizontalCalendarView (){ +typedef NS_ENUM(NSInteger, JTCalendarScrollingDirection) { + JTCalendarScrollingDirectionNone = 0, + JTCalendarScrollingDirectionPrevious, + JTCalendarScrollingDirectionNext +}; + +@interface JTHorizontalCalendarView () { CGSize _lastSize; UIView *_leftView; @@ -26,6 +32,11 @@ @interface JTHorizontalCalendarView (){ JTCalendarPageMode _pageMode; } +// This class needs to hook into some of the UIScrollViewDelegate methods, so after it does that it will forward the messages on to the external delegate. +@property (nonatomic, weak) id externalDelegate; +@property (nonatomic, assign) BOOL animatingOffset; +@property (nonatomic, assign) JTCalendarScrollingDirection scrollingDirection; + @end @implementation JTHorizontalCalendarView @@ -60,8 +71,24 @@ - (void)commonInit self.showsVerticalScrollIndicator = NO; self.pagingEnabled = YES; self.clipsToBounds = YES; + // Deliberately using super here to avoid the overridden setter + [super setDelegate:self]; } +#pragma mark - Properties + +- (void)setDelegate:(id)delegate +{ + self.externalDelegate = delegate; +} + +- (BOOL)scrolling +{ + return self.dragging || self.decelerating || self.animatingOffset; +} + +#pragma mark - + - (void)layoutSubviews { [self resizeViewsIfWidthChanged]; @@ -232,6 +259,8 @@ - (void)loadPreviousPage // Update dayViews becuase current month changed [_rightView reload]; [_centerView reload]; + + self.scrollingDirection = JTCalendarScrollingDirectionPrevious; if(_manager.delegate && [_manager.delegate respondsToSelector:@selector(calendarDidLoadPreviousPage:)]){ [_manager.delegate calendarDidLoadPreviousPage:_manager]; @@ -313,7 +342,9 @@ - (void)loadNextPage // Update dayViews becuase current month changed [_leftView reload]; [_centerView reload]; - + + self.scrollingDirection = JTCalendarScrollingDirectionNext; + if(_manager.delegate && [_manager.delegate respondsToSelector:@selector(calendarDidLoadNextPage:)]){ [_manager.delegate calendarDidLoadNextPage:_manager]; } @@ -446,4 +477,63 @@ - (void)updateMenuDates nextDate:_rightView.date]; } +#pragma mark - UIScrollView + +- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated +{ + if(animated){ + self.animatingOffset = YES; + } + [super setContentOffset:contentOffset animated:animated]; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + if(!decelerate){ + [self notifyEndOfScrolling]; + } + + if(self.externalDelegate){ + [self.externalDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; + } +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView +{ + [self notifyEndOfScrolling]; + + if(self.externalDelegate){ + [self.externalDelegate scrollViewDidEndDecelerating:scrollView]; + } +} + +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView +{ + [self notifyEndOfScrolling]; + + if(self.externalDelegate){ + [self.externalDelegate scrollViewDidEndScrollingAnimation:scrollView]; + } +} + +/** + * Private helper for the above delegate methods + */ +- (void)notifyEndOfScrolling +{ + if(_manager.delegate){ + if(self.scrollingDirection == JTCalendarScrollingDirectionPrevious && [_manager.delegate respondsToSelector:@selector(calendarDidFinishScrollingToPreviousPage:)]){ + [_manager.delegate calendarDidFinishScrollingToPreviousPage:_manager]; + } + else if(self.scrollingDirection == JTCalendarScrollingDirectionNext && [_manager.delegate respondsToSelector:@selector(calendarDidFinishScrollingToNextPage:)]){ + [_manager.delegate calendarDidFinishScrollingToNextPage:_manager]; + } + } + + self.animatingOffset = NO; + self.scrollingDirection = JTCalendarScrollingDirectionNone; +} + @end