From 1be7ecbeb2d2ea40a732fb609c982c5ecf0a9406 Mon Sep 17 00:00:00 2001 From: WenchaoD Date: Fri, 5 Oct 2018 16:18:03 +0800 Subject: [PATCH] Add 'adjustsBoundRectWhenMonthChanges' property to FSCalendar. --- .../FSCalendarScopeExampleViewController.m | 3 +- Example-Objc/HidePlaceholderViewController.m | 2 +- .../StoryboardExampleViewController.m | 6 - FSCalendar/FSCalendar.h | 5 + FSCalendar/FSCalendar.m | 54 +- FSCalendar/FSCalendarCalculator.m | 16 +- FSCalendar/FSCalendarCollectionViewLayout.m | 36 +- FSCalendar/FSCalendarTransitionCoordinator.h | 19 +- FSCalendar/FSCalendarTransitionCoordinator.m | 621 +++++------------- 9 files changed, 212 insertions(+), 550 deletions(-) diff --git a/Example-Objc/FSCalendarScopeExampleViewController.m b/Example-Objc/FSCalendarScopeExampleViewController.m index 3ee18cf..eacbb9d 100644 --- a/Example-Objc/FSCalendarScopeExampleViewController.m +++ b/Example-Objc/FSCalendarScopeExampleViewController.m @@ -49,7 +49,6 @@ NS_ASSUME_NONNULL_END if ([[UIDevice currentDevice].model hasPrefix:@"iPad"]) { self.calendarHeightConstraint.constant = 400; } - [self.calendar selectDate:[NSDate date] scrollToDate:YES]; UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self.calendar action:@selector(handleScopeGesture:)]; @@ -63,7 +62,7 @@ NS_ASSUME_NONNULL_END [self.tableView.panGestureRecognizer requireGestureRecognizerToFail:panGesture]; [self.calendar addObserver:self forKeyPath:@"scope" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:_KVOContext]; - + self.calendar.placeholderType = FSCalendarPlaceholderTypeNone; self.calendar.scope = FSCalendarScopeWeek; // For UITest diff --git a/Example-Objc/HidePlaceholderViewController.m b/Example-Objc/HidePlaceholderViewController.m index 1838bca..9bab179 100644 --- a/Example-Objc/HidePlaceholderViewController.m +++ b/Example-Objc/HidePlaceholderViewController.m @@ -62,8 +62,8 @@ NS_ASSUME_NONNULL_END calendar.backgroundColor = [UIColor whiteColor]; calendar.dataSource = self; calendar.delegate = self; -// calendar.placeholderType = FSCalendarPlaceholderTypeFillHeadTail; calendar.placeholderType = FSCalendarPlaceholderTypeNone; + calendar.adjustsBoundingRectWhenChangingMonths = YES; calendar.currentPage = [self.dateFormatter dateFromString:@"2016-06-01"]; calendar.firstWeekday = 2; calendar.scrollDirection = FSCalendarScrollDirectionVertical; diff --git a/Example-Objc/StoryboardExampleViewController.m b/Example-Objc/StoryboardExampleViewController.m index 2fe5e3c..9a5bf1e 100644 --- a/Example-Objc/StoryboardExampleViewController.m +++ b/Example-Objc/StoryboardExampleViewController.m @@ -153,12 +153,6 @@ NSLog(@"did change to page %@",[self.dateFormatter1 stringFromDate:calendar.currentPage]); } -- (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated -{ - _calendarHeightConstraint.constant = CGRectGetHeight(bounds); - [self.view layoutIfNeeded]; -} - - (CGPoint)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance titleOffsetForDate:(NSDate *)date { if ([self calendar:calendar subtitleForDate:date]) { diff --git a/FSCalendar/FSCalendar.h b/FSCalendar/FSCalendar.h index 738bf9e..990764c 100644 --- a/FSCalendar/FSCalendar.h +++ b/FSCalendar/FSCalendar.h @@ -366,6 +366,11 @@ IB_DESIGNABLE */ @property (assign, nonatomic) IBInspectable BOOL allowsMultipleSelection; +/** + A Boolean value that determines whether the bounding rect changes when the displayed month of the calendar is changed. + */ +@property (assign, nonatomic) IBInspectable BOOL adjustsBoundingRectWhenChangingMonths; + /** A Boolean value that determines whether paging is enabled for the calendar. */ diff --git a/FSCalendar/FSCalendar.m b/FSCalendar/FSCalendar.m index 0572d2f..160c1e3 100644 --- a/FSCalendar/FSCalendar.m +++ b/FSCalendar/FSCalendar.m @@ -67,7 +67,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { @property (weak , nonatomic) FSCalendarHeaderTouchDeliver *deliver; @property (assign, nonatomic) BOOL needsAdjustingViewFrame; -@property (assign, nonatomic) BOOL needsLayoutForWeekMode; @property (assign, nonatomic) BOOL needsRequestingBoundingDates; @property (assign, nonatomic) CGFloat preferredHeaderHeight; @property (assign, nonatomic) CGFloat preferredWeekdayHeight; @@ -191,6 +190,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero]; contentView.backgroundColor = [UIColor clearColor]; + contentView.clipsToBounds = YES; [self addSubview:contentView]; self.contentView = contentView; @@ -330,10 +330,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { _daysContainer.frame = CGRectMake(0, headerHeight+weekdayHeight, self.fs_width, contentHeight); _collectionView.frame = CGRectMake(0, 0, _daysContainer.fs_width, contentHeight); if (needsAdjustingBoundingRect) { - self.transitionCoordinator.state = FSCalendarTransitionStateChanging; - CGRect boundingRect = (CGRect){CGPointZero,[self sizeThatFits:self.frame.size]}; - [self.delegateProxy calendar:self boundingRectWillChange:boundingRect animated:NO]; - self.transitionCoordinator.state = FSCalendarTransitionStateIdle; + [self.transitionCoordinator performBoundingRectTransitionFromMonth:nil toMonth:self.currentPage duration:0]; } break; } @@ -356,11 +353,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { _bottomBorder.frame = CGRectMake(0, self.fs_height, self.fs_width, 1); } - if (_needsLayoutForWeekMode) { - _needsLayoutForWeekMode = NO; - [self.transitionCoordinator performScopeTransitionFromScope:FSCalendarScopeMonth toScope:FSCalendarScopeWeek animated:NO]; - } - } #if TARGET_INTERFACE_BUILDER @@ -376,17 +368,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { - (CGSize)sizeThatFits:(CGSize)size { - switch (self.transitionCoordinator.transition) { - case FSCalendarTransitionNone: - return [self sizeThatFits:size scope:_scope]; - case FSCalendarTransitionWeekToMonth: - if (self.transitionCoordinator.state == FSCalendarTransitionStateChanging) { - return [self sizeThatFits:size scope:FSCalendarScopeMonth]; - } - case FSCalendarTransitionMonthToWeek: - break; - } - return [self sizeThatFits:size scope:FSCalendarScopeWeek]; + return [self sizeThatFits:size scope:self.transitionCoordinator.representingScope]; } - (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope @@ -927,7 +909,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { if (_preferredWeekdayHeight == FSCalendarAutomaticDimension) { if (!self.floatingMode) { CGFloat DIYider = FSCalendarStandardMonthlyPageHeight; - CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height*(1-_showsScopeHandle*0.08); + CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height; _preferredHeaderHeight = (FSCalendarStandardHeaderHeight/DIYider)*contentHeight; _preferredHeaderHeight -= (_preferredHeaderHeight-FSCalendarStandardHeaderHeight)*0.5; } else { @@ -945,7 +927,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { if (_preferredWeekdayHeight == FSCalendarAutomaticDimension) { if (!self.floatingMode) { CGFloat DIYider = FSCalendarStandardMonthlyPageHeight; - CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height*(1-_showsScopeHandle*0.08); + CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height; _preferredWeekdayHeight = (FSCalendarStandardWeekdayHeight/DIYider)*contentHeight; } else { _preferredWeekdayHeight = FSCalendarStandardWeekdayHeight*MAX(1, FSCalendarDeviceIsIPad*1.5); @@ -977,14 +959,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { return _scope == FSCalendarScopeMonth && _scrollEnabled && !_pagingEnabled; } -- (void)setShowsScopeHandle:(BOOL)showsScopeHandle -{ - if (_showsScopeHandle != showsScopeHandle) { - _showsScopeHandle = showsScopeHandle; - [self invalidateLayout]; - } -} - - (UIPanGestureRecognizer *)scopeGesture { if (!_scopeGesture) { @@ -1050,20 +1024,14 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) { if (self.floatingMode) return; if (self.transitionCoordinator.state != FSCalendarTransitionStateIdle) return; - FSCalendarScope prevScope = _scope; - [self willChangeValueForKey:@"scope"]; - _scope = scope; - [self didChangeValueForKey:@"scope"]; - - if (prevScope == scope) return; - - if (!self.hasValidateVisibleLayout && prevScope == FSCalendarScopeMonth && scope == FSCalendarScopeWeek) { - _needsLayoutForWeekMode = YES; + BOOL hasLayout = self.hasValidateVisibleLayout; + if (!hasLayout) { [self setNeedsLayout]; - } else if (self.transitionCoordinator.state == FSCalendarTransitionStateIdle) { - [self.transitionCoordinator performScopeTransitionFromScope:prevScope toScope:scope animated:animated]; + [self.collectionViewLayout invalidateLayout]; + [self layoutIfNeeded]; } - + BOOL needsAnimate = hasLayout && animated; + [self.transitionCoordinator performScopeTransitionFromScope:self.scope toScope:scope animated:needsAnimate]; } - (void)setPlaceholderType:(FSCalendarPlaceholderType)placeholderType diff --git a/FSCalendar/FSCalendarCalculator.m b/FSCalendar/FSCalendarCalculator.m index 3b9d5b5..ae485c0 100644 --- a/FSCalendar/FSCalendarCalculator.m +++ b/FSCalendar/FSCalendarCalculator.m @@ -198,16 +198,12 @@ - (NSInteger)numberOfSections { - if (self.calendar.transitionCoordinator.transition == FSCalendarTransitionWeekToMonth) { - return self.numberOfMonths; - } else { - switch (self.calendar.transitionCoordinator.representingScope) { - case FSCalendarScopeMonth: { - return self.numberOfMonths; - } - case FSCalendarScopeWeek: { - return self.numberOfWeeks; - } + switch (self.calendar.transitionCoordinator.representingScope) { + case FSCalendarScopeMonth: { + return self.numberOfMonths; + } + case FSCalendarScopeWeek: { + return self.numberOfWeeks; } } } diff --git a/FSCalendar/FSCalendarCollectionViewLayout.m b/FSCalendar/FSCalendarCollectionViewLayout.m index e1357e1..f69768b 100644 --- a/FSCalendar/FSCalendarCollectionViewLayout.m +++ b/FSCalendar/FSCalendarCollectionViewLayout.m @@ -41,6 +41,7 @@ @property (strong, nonatomic) NSMutableDictionary *rowSeparatorAttributes; - (void)didReceiveNotifications:(NSNotification *)notification; +- (CGFloat)calculateRowOffset:(NSInteger)row totalRows:(NSInteger)totalRows; @end @@ -404,21 +405,26 @@ FSCalendarCoordinate coordinate = [self.calendar.calculator coordinateForIndexPath:indexPath]; NSInteger column = coordinate.column; NSInteger row = coordinate.row; + NSInteger numberOfRows = [self.calendar.calculator numberOfRowsInSection:indexPath.section]; UICollectionViewLayoutAttributes *attributes = self.itemAttributes[indexPath]; if (!attributes) { attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; CGRect frame = ({ + CGFloat width = self.widths[column]; + CGFloat height = self.heights[row]; CGFloat x, y; switch (self.scrollDirection) { case UICollectionViewScrollDirectionHorizontal: { x = self.lefts[column] + indexPath.section * self.collectionView.fs_width; - y = self.tops[row]; + y = [self calculateRowOffset:row totalRows:numberOfRows]; break; } case UICollectionViewScrollDirectionVertical: { x = self.lefts[column]; if (!self.calendar.floatingMode) { - y = self.tops[row] + indexPath.section * self.collectionView.fs_height; + CGFloat sectionTop = indexPath.section * self.collectionView.fs_height; + CGFloat rowOffset = [self calculateRowOffset:row totalRows:numberOfRows]; + y = sectionTop + rowOffset; } else { y = self.sectionTops[indexPath.section] + self.headerReferenceSize.height + self.tops[row]; } @@ -427,8 +433,6 @@ default: break; } - CGFloat width = self.widths[column]; - CGFloat height = self.heights[row]; CGRect frame = CGRectMake(x, y, width, height); frame; }); @@ -466,15 +470,16 @@ attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:kFSCalendarSeparatorInterRows withIndexPath:indexPath]; CGFloat x, y; if (!self.calendar.floatingMode) { + CGFloat rowOffset = [self calculateRowOffset:coordinate.row totalRows:[self.calendar.calculator numberOfRowsInSection:indexPath.section]] + self.heights[coordinate.row]; switch (self.scrollDirection) { case UICollectionViewScrollDirectionHorizontal: { x = self.lefts[coordinate.column] + indexPath.section * self.collectionView.fs_width; - y = self.tops[coordinate.row]+self.heights[coordinate.row]; + y = rowOffset; break; } case UICollectionViewScrollDirectionVertical: { x = 0; - y = self.tops[coordinate.row]+self.heights[coordinate.row] + indexPath.section * self.collectionView.fs_height; + y = indexPath.section * self.collectionView.fs_height + rowOffset; break; } default: @@ -526,6 +531,25 @@ #pragma mark - Private functions +- (CGFloat)calculateRowOffset:(NSInteger)row totalRows:(NSInteger)totalRows +{ + if (self.calendar.adjustsBoundingRectWhenChangingMonths) { + return self.tops[row]; + } + CGFloat height = self.heights[row]; + switch (totalRows) { + case 4: + case 5: { + CGFloat contentHeight = self.collectionView.fs_height - self.sectionInsets.top - self.sectionInsets.bottom; + CGFloat rowSpan = contentHeight/totalRows; + return (row + 0.5) * rowSpan - height * 0.5 + self.sectionInsets.top; + } + case 6: + default: + return self.tops[row]; + } +} + - (NSInteger)searchStartSection:(CGRect)rect :(NSInteger)left :(NSInteger)right { NSInteger mid = left + (right-left)/2; diff --git a/FSCalendar/FSCalendarTransitionCoordinator.h b/FSCalendar/FSCalendarTransitionCoordinator.h index 4d7ce84..9babddb 100644 --- a/FSCalendar/FSCalendarTransitionCoordinator.h +++ b/FSCalendar/FSCalendarTransitionCoordinator.h @@ -10,11 +10,6 @@ #import "FSCalendarCollectionView.h" #import "FSCalendarCollectionViewLayout.h" -typedef NS_ENUM(NSUInteger, FSCalendarTransition) { - FSCalendarTransitionNone, - FSCalendarTransitionMonthToWeek, - FSCalendarTransitionWeekToMonth -}; typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) { FSCalendarTransitionStateIdle, FSCalendarTransitionStateChanging, @@ -23,11 +18,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) { @interface FSCalendarTransitionCoordinator : NSObject -@property (weak, nonatomic) FSCalendar *calendar; -@property (weak, nonatomic) FSCalendarCollectionView *collectionView; -@property (weak, nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout; - -@property (assign, nonatomic) FSCalendarTransition transition; @property (assign, nonatomic) FSCalendarTransitionState state; @property (assign, nonatomic) CGSize cachedMonthSize; @@ -38,6 +28,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) { - (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated; - (void)performBoundingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration; +- (CGRect)boundingRectForScope:(FSCalendarScope)scope page:(NSDate *)page; - (void)handleScopeGesture:(id)sender; @@ -50,9 +41,11 @@ typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) { @property (assign, nonatomic) CGRect targetBounds; @property (strong, nonatomic) NSDate *sourcePage; @property (strong, nonatomic) NSDate *targetPage; -@property (assign, nonatomic) NSInteger focusedRowNumber; -@property (assign, nonatomic) NSDate *focusedDate; -@property (strong, nonatomic) NSDate *firstDayOfMonth; +@property (assign, nonatomic) NSInteger focusedRow; +@property (strong, nonatomic) NSDate *focusedDate; +@property (assign, nonatomic) FSCalendarScope targetScope; +- (void)revert; + @end diff --git a/FSCalendar/FSCalendarTransitionCoordinator.m b/FSCalendar/FSCalendarTransitionCoordinator.m index 9124ce7..6d988cd 100644 --- a/FSCalendar/FSCalendarTransitionCoordinator.m +++ b/FSCalendar/FSCalendarTransitionCoordinator.m @@ -9,20 +9,19 @@ #import "FSCalendarTransitionCoordinator.h" #import "FSCalendarExtensions.h" #import "FSCalendarDynamicHeader.h" -#import @interface FSCalendarTransitionCoordinator () -@property (readonly, nonatomic) FSCalendarTransitionAttributes *transitionAttributes; -@property (strong , nonatomic) FSCalendarTransitionAttributes *pendingAttributes; -@property (assign , nonatomic) CGFloat lastTranslation; +@property (weak, nonatomic) FSCalendarCollectionView *collectionView; +@property (weak, nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout; +@property (weak, nonatomic) FSCalendar *calendar; + +@property (strong, nonatomic) FSCalendarTransitionAttributes *transitionAttributes; + +- (FSCalendarTransitionAttributes *)createTransitionAttributesTargetingScope:(FSCalendarScope)targetScope; - (void)performTransitionCompletionAnimated:(BOOL)animated; -- (void)performTransitionCompletion:(FSCalendarTransition)transition animated:(BOOL)animated; -- (void)performAlphaAnimationFrom:(CGFloat)fromAlpha to:(CGFloat)toAlpha duration:(CGFloat)duration exception:(NSInteger)exception completion:(void(^)(void))completion; -- (void)performForwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress; -- (void)performBackwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress; - (void)performAlphaAnimationWithProgress:(CGFloat)progress; - (void)performPathAnimationWithProgress:(CGFloat)progress; @@ -30,8 +29,6 @@ - (void)scopeTransitionDidUpdate:(UIPanGestureRecognizer *)panGesture; - (void)scopeTransitionDidEnd:(UIPanGestureRecognizer *)panGesture; -- (CGRect)boundingRectForScope:(FSCalendarScope)scope page:(NSDate *)page; - - (void)boundingRectWillChange:(CGRect)targetBounds animated:(BOOL)animated; @end @@ -115,34 +112,18 @@ if (self.state != FSCalendarTransitionStateIdle) return; CGPoint velocity = [panGesture velocityInView:panGesture.view]; - switch (self.calendar.scope) { - case FSCalendarScopeMonth: { - if (velocity.y < 0) { - self.state = FSCalendarTransitionStateChanging; - self.transition = FSCalendarTransitionMonthToWeek; - } - break; - } - case FSCalendarScopeWeek: { - if (velocity.y > 0) { - self.state = FSCalendarTransitionStateChanging; - self.transition = FSCalendarTransitionWeekToMonth; - } - break; - } - default: - break; + if (self.calendar.scope == FSCalendarScopeMonth && velocity.y >= 0) { + return; } - if (self.state != FSCalendarTransitionStateChanging) return; + if (self.calendar.scope == FSCalendarScopeWeek && velocity.y <= 0) { + return; + } + self.state = FSCalendarTransitionStateChanging; - self.pendingAttributes = self.transitionAttributes; - self.lastTranslation = [panGesture translationInView:panGesture.view].y; + self.transitionAttributes = [self createTransitionAttributesTargetingScope:1-self.calendar.scope]; - if (self.transition == FSCalendarTransitionWeekToMonth) { - [self.calendar fs_setVariable:self.pendingAttributes.targetPage forKey:@"_currentPage"]; - [self prelayoutForWeekToMonthTransition]; - self.collectionView.fs_top = -self.pendingAttributes.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height; - + if (self.transitionAttributes.targetScope == FSCalendarScopeMonth) { + [self prepareWeekToMonthTransition]; } } @@ -150,40 +131,16 @@ { if (self.state != FSCalendarTransitionStateChanging) return; - CGFloat translation = [panGesture translationInView:panGesture.view].y; - - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - switch (self.transition) { - case FSCalendarTransitionMonthToWeek: { - CGFloat progress = ({ - CGFloat minTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds); - translation = MAX(minTranslation, translation); - translation = MIN(0, translation); - CGFloat progress = translation/minTranslation; - progress; - }); - [self performAlphaAnimationWithProgress:progress]; - [self performPathAnimationWithProgress:progress]; - break; - } - case FSCalendarTransitionWeekToMonth: { - CGFloat progress = ({ - CGFloat maxTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds); - translation = MIN(maxTranslation, translation); - translation = MAX(0, translation); - CGFloat progress = translation/maxTranslation; - progress; - }); - [self performAlphaAnimationWithProgress:progress]; - [self performPathAnimationWithProgress:progress]; - break; - } - default: - break; - } - [CATransaction commit]; - self.lastTranslation = translation; + CGFloat translation = ABS([panGesture translationInView:panGesture.view].y); + CGFloat progress = ({ + CGFloat maxTranslation = ABS(CGRectGetHeight(self.transitionAttributes.targetBounds) - CGRectGetHeight(self.transitionAttributes.sourceBounds)); + translation = MIN(maxTranslation, translation); + translation = MAX(0, translation); + CGFloat progress = translation/maxTranslation; + progress; + }); + [self performAlphaAnimationWithProgress:progress]; + [self performPathAnimationWithProgress:progress]; } - (void)scopeTransitionDidEnd:(UIPanGestureRecognizer *)panGesture @@ -195,148 +152,48 @@ CGFloat translation = [panGesture translationInView:panGesture.view].y; CGFloat velocity = [panGesture velocityInView:panGesture.view].y; - switch (self.transition) { - case FSCalendarTransitionMonthToWeek: { - CGFloat progress = ({ - CGFloat minTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds); - translation = MAX(minTranslation, translation); - translation = MIN(0, translation); - CGFloat progress = translation/minTranslation; - progress; - }); - if (velocity >= 0) { - [self performBackwardTransition:self.transition fromProgress:progress]; - } else { - [self performForwardTransition:self.transition fromProgress:progress]; - } - break; - } - case FSCalendarTransitionWeekToMonth: { - CGFloat progress = ({ - CGFloat maxTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds); - translation = MAX(0, translation); - translation = MIN(maxTranslation, translation); - CGFloat progress = translation/maxTranslation; - progress; - }); - if (velocity >= 0) { - [self performForwardTransition:self.transition fromProgress:progress]; - } else { - [self performBackwardTransition:self.transition fromProgress:progress]; - } - break; - } - default: - break; + CGFloat progress = ({ + CGFloat maxTranslation = CGRectGetHeight(self.transitionAttributes.targetBounds) - CGRectGetHeight(self.transitionAttributes.sourceBounds); + translation = MAX(0, translation); + translation = MIN(maxTranslation, translation); + CGFloat progress = translation/maxTranslation; + progress; + }); + if (velocity * translation < 0) { + [self.transitionAttributes revert]; } - + [self performTransition:self.transitionAttributes.targetScope fromProgress:progress toProgress:1.0 animated:YES]; } #pragma mark - Public methods - (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated { - if (fromScope == toScope) return; - - self.transition = ({ - FSCalendarTransition transition = FSCalendarTransitionNone; - if (fromScope == FSCalendarScopeMonth && toScope == FSCalendarScopeWeek) { - transition = FSCalendarTransitionMonthToWeek; - } else if (fromScope == FSCalendarScopeWeek && toScope == FSCalendarScopeMonth) { - transition = FSCalendarTransitionWeekToMonth; - } - transition; - }); - + if (fromScope == toScope) { + [self.calendar willChangeValueForKey:@"scope"]; + [self.calendar fs_setUnsignedIntegerVariable:toScope forKey:@"_scope"]; + [self.calendar didChangeValueForKey:@"scope"]; + return; + } // Start transition self.state = FSCalendarTransitionStateFinishing; - FSCalendarTransitionAttributes *attr = self.transitionAttributes; - self.pendingAttributes = attr; - - switch (self.transition) { - - case FSCalendarTransitionMonthToWeek: { - - [self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"]; - self.calendar.contentView.clipsToBounds = YES; - - if (animated) { - CGFloat duration = 0.3; - - [self performAlphaAnimationFrom:1 to:0 duration:0.22 exception:attr.focusedRowNumber completion:^{ - [self performTransitionCompletionAnimated:animated]; - }]; - - if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) { - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationsEnabled:YES]; - [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; - [UIView setAnimationDuration:duration]; - self.collectionView.fs_top = -attr.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height; - [self boundingRectWillChange:attr.targetBounds animated:animated]; - [UIView commitAnimations]; - } - - } else { - - [self performTransitionCompletionAnimated:animated]; - [self boundingRectWillChange:attr.targetBounds animated:animated]; - - } - - break; - } - - case FSCalendarTransitionWeekToMonth: { - - [self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"]; - - [self prelayoutForWeekToMonthTransition]; - - if (animated) { - - CGFloat duration = 0.3; - - [self performAlphaAnimationFrom:0 to:1 duration:duration exception:attr.focusedRowNumber completion:^{ - [self performTransitionCompletionAnimated:animated]; - }]; - - [CATransaction begin]; - [CATransaction setDisableActions:NO]; - if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) { - self.collectionView.fs_top = -attr.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height; - [UIView beginAnimations:nil context:nil]; - [UIView setAnimationsEnabled:YES]; - [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; - [UIView setAnimationDuration:duration]; - self.collectionView.fs_top = 0; - [self boundingRectWillChange:attr.targetBounds animated:animated]; - [UIView commitAnimations]; - } - [CATransaction commit]; - - } else { - - [self performTransitionCompletionAnimated:animated]; - [self boundingRectWillChange:attr.targetBounds animated:animated]; - - } - break; - } - default: - break; + FSCalendarTransitionAttributes *attr = [self createTransitionAttributesTargetingScope:toScope]; + self.transitionAttributes = attr; + if (toScope == FSCalendarScopeMonth) { + [self prepareWeekToMonthTransition]; } - + [self performTransition:self.transitionAttributes.targetScope fromProgress:0 toProgress:1 animated:animated]; } - (void)performBoundingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration { + if (!self.calendar.adjustsBoundingRectWhenChangingMonths) return; if (self.calendar.scope != FSCalendarScopeMonth) return; NSInteger lastRowCount = [self.calendar.calculator numberOfRowsInMonth:fromMonth]; NSInteger currentRowCount = [self.calendar.calculator numberOfRowsInMonth:toMonth]; if (lastRowCount != currentRowCount) { CGFloat animationDuration = duration; - CGRect bounds = (CGRect){CGPointZero,[self.calendar sizeThatFits:self.calendar.frame.size scope:FSCalendarScopeMonth]}; + CGRect bounds = [self boundingRectForScope:FSCalendarScopeMonth page:toMonth]; self.state = FSCalendarTransitionStateChanging; void (^completion)(BOOL) = ^(BOOL finished) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(MAX(0, duration-animationDuration) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @@ -362,16 +219,8 @@ - (void)performTransitionCompletionAnimated:(BOOL)animated { - [self performTransitionCompletion:self.transition animated:animated]; -} - -- (void)performTransitionCompletion:(FSCalendarTransition)transition animated:(BOOL)animated -{ - switch (transition) { - case FSCalendarTransitionMonthToWeek: { - [self.calendar.visibleCells enumerateObjectsUsingBlock:^(UICollectionViewCell *obj, NSUInteger idx, BOOL * stop) { - obj.contentView.layer.opacity = 1; - }]; + switch (self.transitionAttributes.targetScope) { + case FSCalendarScopeWeek: { self.collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.calendar.calendarHeaderView.scrollDirection = self.collectionViewLayout.scrollDirection; self.calendar.needsAdjustingViewFrame = YES; @@ -379,120 +228,56 @@ [self.calendar.calendarHeaderView reloadData]; break; } - case FSCalendarTransitionWeekToMonth: { + case FSCalendarScopeMonth: { self.calendar.needsAdjustingViewFrame = YES; - [self.calendar.visibleCells enumerateObjectsUsingBlock:^(UICollectionViewCell *obj, NSUInteger idx, BOOL * stop) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - obj.contentView.layer.opacity = 1; - [CATransaction commit]; - [obj.contentView.layer removeAnimationForKey:@"opacity"]; - }]; break; } default: break; } self.state = FSCalendarTransitionStateIdle; - self.transition = FSCalendarTransitionNone; - self.calendar.contentView.clipsToBounds = NO; - self.pendingAttributes = nil; + self.transitionAttributes = nil; [self.calendar setNeedsLayout]; [self.calendar layoutIfNeeded]; } -- (FSCalendarTransitionAttributes *)transitionAttributes +- (FSCalendarTransitionAttributes *)createTransitionAttributesTargetingScope:(FSCalendarScope)targetScope { FSCalendarTransitionAttributes *attributes = [[FSCalendarTransitionAttributes alloc] init]; attributes.sourceBounds = self.calendar.bounds; attributes.sourcePage = self.calendar.currentPage; - switch (self.transition) { - - case FSCalendarTransitionMonthToWeek: { - - NSDate *focusedDate = ({ - NSArray *candidates = ({ - NSMutableArray *dates = self.calendar.selectedDates.reverseObjectEnumerator.allObjects.mutableCopy; - if (self.calendar.today) { - [dates addObject:self.calendar.today]; - } - if (self.calendar.currentPage) { - [dates addObject:self.calendar.currentPage]; - } - dates.copy; - }); - NSArray *visibleCandidates = [candidates filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDate * _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:evaluatedObject scope:FSCalendarScopeMonth]; - NSInteger currentSection = [self.calendar.calculator indexPathForDate:self.calendar.currentPage scope:FSCalendarScopeMonth].section; - return indexPath.section == currentSection; - }]]; - NSDate *date = visibleCandidates.firstObject; - date; - }); - NSInteger focusedRow = [self.calendar.calculator coordinateForIndexPath:[self.calendar.calculator indexPathForDate:focusedDate scope:FSCalendarScopeMonth]].row; - - NSDate *currentPage = self.calendar.currentPage; - NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:currentPage scope:FSCalendarScopeMonth]; - NSDate *monthHead = [self.calendar.calculator monthHeadForSection:indexPath.section]; - NSDate *targetPage = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:focusedRow*7 toDate:monthHead options:0]; - - attributes.focusedRowNumber = focusedRow; - attributes.focusedDate = focusedDate; - attributes.targetPage = targetPage; - attributes.targetBounds = [self boundingRectForScope:FSCalendarScopeWeek page:attributes.targetPage]; - - break; - } - case FSCalendarTransitionWeekToMonth: { - - NSInteger focusedRow = 0; - NSDate *currentPage = self.calendar.currentPage; - - NSDate *focusedDate = ({ - NSArray *candidates = ({ - NSMutableArray *dates = self.calendar.selectedDates.reverseObjectEnumerator.allObjects.mutableCopy; - if (self.calendar.today) { - [dates addObject:self.calendar.today]; - } - if (self.calendar.currentPage) { - [dates addObject:[self.calendar.gregorian fs_lastDayOfWeek:currentPage]]; - } - dates.copy; - }); - NSArray *visibleCandidates = [candidates filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDate * _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:evaluatedObject scope:FSCalendarScopeWeek]; - NSInteger currentSection = [self.calendar.calculator indexPathForDate:self.calendar.currentPage scope:FSCalendarScopeWeek].section; - return indexPath.section == currentSection; - }]]; - NSDate *date = visibleCandidates.firstObject; - date; - }); - - NSDate *firstDayOfMonth = [self.calendar.gregorian fs_firstDayOfMonth:focusedDate]; - attributes.focusedDate = focusedDate; - firstDayOfMonth = firstDayOfMonth ?: [self.calendar.gregorian fs_firstDayOfMonth:currentPage]; - NSInteger numberOfPlaceholdersForPrev = [self.calendar.calculator numberOfHeadPlaceholdersForMonth:firstDayOfMonth]; - NSDate *firstDateOfPage = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:-numberOfPlaceholdersForPrev toDate:firstDayOfMonth options:0]; - - for (int i = 0; i < 6; i++) { - NSDate *currentRow = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitWeekOfYear value:i toDate:firstDateOfPage options:0]; - if ([self.calendar.gregorian isDate:currentRow inSameDayAsDate:currentPage]) { - focusedRow = i; - currentPage = firstDayOfMonth; - break; - } + attributes.targetScope = targetScope; + attributes.focusedDate = ({ + NSArray *candidates = ({ + NSMutableArray *dates = self.calendar.selectedDates.reverseObjectEnumerator.allObjects.mutableCopy; + if (self.calendar.today) { + [dates addObject:self.calendar.today]; } - attributes.focusedRowNumber = focusedRow; - attributes.targetPage = currentPage; - attributes.firstDayOfMonth = firstDayOfMonth; - - attributes.targetBounds = [self boundingRectForScope:FSCalendarScopeMonth page:attributes.targetPage]; - - break; - } - default: - break; - } + if (targetScope == FSCalendarScopeWeek) { + [dates addObject:self.calendar.currentPage]; + } else { + [dates addObject:[self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:3 toDate:self.calendar.currentPage options:0]]; + } + dates.copy; + }); + NSArray *visibleCandidates = [candidates filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDate * _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:evaluatedObject scope:1-targetScope]; + NSInteger currentSection = [self.calendar.calculator indexPathForDate:self.calendar.currentPage scope:1-targetScope].section; + return indexPath.section == currentSection; + }]]; + NSDate *date = visibleCandidates.firstObject; + date; + }); + attributes.focusedRow = ({ + NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:attributes.focusedDate scope:FSCalendarScopeMonth]; + FSCalendarCoordinate coordinate = [self.calendar.calculator coordinateForIndexPath:indexPath]; + coordinate.row; + }); + attributes.targetPage = ({ + NSDate *targetPage = targetScope == FSCalendarScopeMonth ? [self.calendar.gregorian fs_firstDayOfMonth:attributes.focusedDate] : [self.calendar.gregorian fs_middleDayOfWeek:attributes.focusedDate]; + targetPage; + }); + attributes.targetBounds = [self boundingRectForScope:attributes.targetScope page:attributes.targetPage]; return attributes; } @@ -518,15 +303,7 @@ CGSize contentSize; switch (scope) { case FSCalendarScopeMonth: { - if (self.calendar.placeholderType == FSCalendarPlaceholderTypeFillSixRows) { - contentSize = self.cachedMonthSize; - } else { - CGFloat padding = self.calendar.collectionViewLayout.sectionInsets.top + self.calendar.collectionViewLayout.sectionInsets.bottom; - contentSize = CGSizeMake(self.calendar.fs_width, - self.calendar.preferredHeaderHeight+ - self.calendar.preferredWeekdayHeight+ - ([self.calendar.calculator numberOfRowsInMonth:page]*self.calendar.collectionViewLayout.estimatedItemSize.height)+padding); - } + contentSize = self.calendar.adjustsBoundingRectWhenChangingMonths ? [self.calendar sizeThatFits:self.calendar.frame.size scope:scope] : self.cachedMonthSize; break; } case FSCalendarScopeWeek: { @@ -534,7 +311,7 @@ break; } } - return (CGRect){CGPointZero,contentSize}; + return (CGRect){CGPointZero, contentSize}; } - (void)boundingRectWillChange:(CGRect)targetBounds animated:(BOOL)animated @@ -544,197 +321,103 @@ [[self.calendar valueForKey:@"delegateProxy"] calendar:self.calendar boundingRectWillChange:targetBounds animated:animated]; } -- (void)performForwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress +- (void)performTransition:(FSCalendarScope)targetScope fromProgress:(CGFloat)fromProgress toProgress:(CGFloat)toProgress animated:(BOOL)animated { - FSCalendarTransitionAttributes *attr = self.pendingAttributes; - switch (transition) { - case FSCalendarTransitionMonthToWeek: { - - [self.calendar willChangeValueForKey:@"scope"]; - [self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeWeek forKey:@"_scope"]; - [self.calendar didChangeValueForKey:@"scope"]; - - [self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"]; - - self.calendar.contentView.clipsToBounds = YES; - - CGFloat currentAlpha = MAX(1-progress*1.1,0); - CGFloat duration = 0.3; - [self performAlphaAnimationFrom:currentAlpha to:0 duration:0.22 exception:attr.focusedRowNumber completion:^{ + FSCalendarTransitionAttributes *attr = self.transitionAttributes; + + [self.calendar willChangeValueForKey:@"scope"]; + [self.calendar fs_setUnsignedIntegerVariable:targetScope forKey:@"_scope"]; + if (targetScope == FSCalendarScopeWeek) { + [self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"]; + } + [self.calendar didChangeValueForKey:@"scope"]; + + if (animated) { + if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)])) { + [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + [self performAlphaAnimationWithProgress:toProgress]; + self.collectionView.fs_top = [self calculateOffsetForProgress:toProgress]; + [self boundingRectWillChange:attr.targetBounds animated:YES]; + } completion:^(BOOL finished) { [self performTransitionCompletionAnimated:YES]; }]; - - if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) { - [UIView beginAnimations:@"delegateTranslation" context:"translation"]; - [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; - [UIView setAnimationDuration:duration]; - self.collectionView.fs_top = -attr.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height; - [self boundingRectWillChange:attr.targetBounds animated:YES]; - [UIView commitAnimations]; - } - - break; } - case FSCalendarTransitionWeekToMonth: { - - [self.calendar willChangeValueForKey:@"scope"]; - [self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeMonth forKey:@"_scope"]; - [self.calendar didChangeValueForKey:@"scope"]; - - [self performAlphaAnimationFrom:progress to:1 duration:0.4 exception:attr.focusedRowNumber completion:^{ - [self performTransitionCompletionAnimated:YES]; - }]; - - CGFloat duration = 0.3; - [CATransaction begin]; - [CATransaction setDisableActions:NO]; - - if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) { - [UIView beginAnimations:@"delegateTranslation" context:"translation"]; - [UIView setAnimationsEnabled:YES]; - [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; - [UIView setAnimationDuration:duration]; - self.collectionView.fs_top = 0; - [self boundingRectWillChange:attr.targetBounds animated:YES]; - [UIView commitAnimations]; - } - [CATransaction commit]; - break; - } - default: - break; - } -} - -- (void)performBackwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress -{ - switch (transition) { - case FSCalendarTransitionMonthToWeek: { - - [self.calendar willChangeValueForKey:@"scope"]; - [self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeMonth forKey:@"_scope"]; - [self.calendar didChangeValueForKey:@"scope"]; - - [self performAlphaAnimationFrom:MAX(1-progress*1.1,0) to:1 duration:0.3 exception:self.pendingAttributes.focusedRowNumber completion:^{ - [self.calendar.visibleCells enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - obj.contentView.layer.opacity = 1; - [obj.contentView.layer removeAnimationForKey:@"opacity"]; - }]; - self.pendingAttributes = nil; - self.state = FSCalendarTransitionStateIdle; - }]; - - if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) { - [UIView beginAnimations:@"delegateTranslation" context:"translation"]; - [UIView setAnimationsEnabled:YES]; - [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; - [UIView setAnimationDuration:0.3]; - self.collectionView.fs_top = 0; - [self boundingRectWillChange:self.pendingAttributes.sourceBounds animated:YES]; - [UIView commitAnimations]; - } - break; - } - case FSCalendarTransitionWeekToMonth: { - - [self.calendar willChangeValueForKey:@"scope"]; - [self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeWeek forKey:@"_scope"]; - [self.calendar didChangeValueForKey:@"scope"]; - - [self performAlphaAnimationFrom:progress to:0 duration:0.3 exception:self.pendingAttributes.focusedRowNumber completion:^{ - [self.calendar fs_setVariable:self.pendingAttributes.sourcePage forKey:@"_currentPage"]; - [self performTransitionCompletion:FSCalendarTransitionMonthToWeek animated:YES]; - }]; - - if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) { - [UIView beginAnimations:@"delegateTranslation" context:"translation"]; - [UIView setAnimationsEnabled:YES]; - [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; - [UIView setAnimationDuration:0.3]; - self.collectionView.fs_top = (-self.pendingAttributes.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height); - [self boundingRectWillChange:self.pendingAttributes.sourceBounds animated:YES]; - [UIView commitAnimations]; - } - break; - } - default: - break; - } -} - -- (void)performAlphaAnimationFrom:(CGFloat)fromAlpha to:(CGFloat)toAlpha duration:(CGFloat)duration exception:(NSInteger)exception completion:(void(^)(void))completion; -{ - [self.calendar.visibleCells enumerateObjectsUsingBlock:^(FSCalendarCell *cell, NSUInteger idx, BOOL *stop) { - if (CGRectContainsPoint(self.collectionView.bounds, cell.center)) { - BOOL shouldPerformAlpha = NO; - NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; - NSInteger row = [self.calendar.calculator coordinateForIndexPath:indexPath].row; - shouldPerformAlpha = row != exception; - if (shouldPerformAlpha) { - CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacity.duration = duration; - opacity.fromValue = @(fromAlpha); - opacity.toValue = @(toAlpha); - opacity.removedOnCompletion = NO; - opacity.fillMode = kCAFillModeForwards; - [cell.contentView.layer addAnimation:opacity forKey:@"opacity"]; - } - } - }]; - if (completion) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - completion(); - }); + } else { + [self performTransitionCompletionAnimated:animated]; + [self boundingRectWillChange:attr.targetBounds animated:animated]; } } - (void)performAlphaAnimationWithProgress:(CGFloat)progress { - CGFloat opacity = self.transition == FSCalendarTransitionMonthToWeek ? MAX((1-progress*1.1),0) : progress; - [self.calendar.visibleCells enumerateObjectsUsingBlock:^(FSCalendarCell *cell, NSUInteger idx, BOOL *stop) { - if (CGRectContainsPoint(self.collectionView.bounds, cell.center)) { - BOOL shouldPerformAlpha = NO; - NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; - NSInteger row = [self.calendar.calculator coordinateForIndexPath:indexPath].row; - shouldPerformAlpha = row != self.pendingAttributes.focusedRowNumber; - if (shouldPerformAlpha) { - cell.contentView.layer.opacity = opacity; - } + CGFloat opacity = self.transitionAttributes.targetScope == FSCalendarScopeWeek ? MAX((1-progress*1.1), 0) : progress; + NSArray *surroundingCells = [self.calendar.visibleCells filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FSCalendarCell * _Nullable cell, NSDictionary * _Nullable bindings) { + if (!CGRectContainsPoint(self.collectionView.bounds, cell.center)) { + return NO; } - }]; + NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell]; + NSInteger row = [self.calendar.calculator coordinateForIndexPath:indexPath].row; + return row != self.transitionAttributes.focusedRow; + }]]; + [surroundingCells setValue:@(opacity) forKey:@"alpha"]; } - (void)performPathAnimationWithProgress:(CGFloat)progress { - CGFloat targetHeight = CGRectGetHeight(self.pendingAttributes.targetBounds); - CGFloat sourceHeight = CGRectGetHeight(self.pendingAttributes.sourceBounds); + CGFloat targetHeight = CGRectGetHeight(self.transitionAttributes.targetBounds); + CGFloat sourceHeight = CGRectGetHeight(self.transitionAttributes.sourceBounds); CGFloat currentHeight = sourceHeight - (sourceHeight-targetHeight)*progress; - CGRect currentBounds = CGRectMake(0, 0, CGRectGetWidth(self.pendingAttributes.targetBounds), currentHeight); - self.collectionView.fs_top = (-self.pendingAttributes.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height)*(self.transition == FSCalendarTransitionMonthToWeek?progress:(1-progress)); + CGRect currentBounds = CGRectMake(0, 0, CGRectGetWidth(self.transitionAttributes.targetBounds), currentHeight); + self.collectionView.fs_top = [self calculateOffsetForProgress:progress]; [self boundingRectWillChange:currentBounds animated:NO]; - if (self.transition == FSCalendarTransitionWeekToMonth) { + if (self.transitionAttributes.targetScope == FSCalendarScopeMonth) { self.calendar.contentView.fs_height = targetHeight; } } - -- (void)prelayoutForWeekToMonthTransition +- (CGFloat)calculateOffsetForProgress:(CGFloat)progress { - self.calendar.contentView.clipsToBounds = YES; - self.calendar.contentView.fs_height = CGRectGetHeight(self.pendingAttributes.targetBounds); + NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:self.transitionAttributes.focusedDate scope:FSCalendarScopeMonth]; + CGRect frame = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath].frame; + CGFloat ratio = self.transitionAttributes.targetScope == FSCalendarScopeWeek ? progress : (1 - progress); + CGFloat offset = (-frame.origin.y + self.collectionViewLayout.sectionInsets.top) * ratio; + return offset; +} + +- (void)prepareWeekToMonthTransition +{ + [self.calendar fs_setVariable:self.transitionAttributes.targetPage forKey:@"_currentPage"]; + + self.calendar.contentView.fs_height = CGRectGetHeight(self.transitionAttributes.targetBounds); self.collectionViewLayout.scrollDirection = (UICollectionViewScrollDirection)self.calendar.scrollDirection; self.calendar.calendarHeaderView.scrollDirection = self.collectionViewLayout.scrollDirection; self.calendar.needsAdjustingViewFrame = YES; - [self.calendar setNeedsLayout]; + + [CATransaction begin]; + [CATransaction setDisableActions:NO]; [self.collectionView reloadData]; [self.calendar.calendarHeaderView reloadData]; [self.calendar layoutIfNeeded]; + [CATransaction commit]; + + self.collectionView.fs_top = [self calculateOffsetForProgress:0]; } @end @implementation FSCalendarTransitionAttributes +- (void)revert +{ + CGRect tempRect = self.sourceBounds; + self.sourceBounds = self.targetBounds; + self.targetBounds = tempRect; + NSDate *tempDate = self.sourcePage; + self.sourcePage = self.targetPage; + self.targetPage = tempDate; + + self.targetScope = 1 - self.targetScope; +} + @end -