// // TFCoveragePageTurningController.m // TFReader // // Created by 谢腾飞 on 2020/12/21. // Copyright © 2020 xtfei_2011@126.com. All rights reserved. // #import "TFCoveragePageTurningController.h" #define AnimateDuration 0.2 @interface TFCoveragePageTurningController () /*** 左右拉手势 ***/ @property(nonatomic ,strong) UIPanGestureRecognizer *pan; /*** 点击手势 ***/ @property(nonatomic ,strong) UITapGestureRecognizer *tap; /*** 手势触发点在左边 辨认方向 左边拿上一个控制器 右边拿下一个控制器 ***/ @property(nonatomic) BOOL isLeft; /*** 判断执行pan手势 ***/ @property(nonatomic) BOOL isPan; /*** 手势是否重新开始识别 ***/ @property(nonatomic) BOOL isPanBegin; @property(nonatomic) BOOL isAnimating; /*** 临时控制器 通过代理获取回来的控制器 还没有完全展示出来的控制器 ***/ @property(nonatomic ,strong ,nullable) UIViewController *tempController; /*** 临时View 通过代理获取回来的View 还没有完全展示出来的View ***/ @property(nonatomic ,strong ,nullable) UIView *tempView; @property(nonatomic) NSInteger numberOfPages; @property(nonatomic) NSInteger currentIndex; @property(nonatomic ,readonly) NSInteger lastIndex; @property(nonatomic ,readonly) NSInteger nextIndex; /*** 内部切换控制 能否切换到上一页 ***/ @property(nonatomic) BOOL scrollToLastEnabled; /*** 内部切换控制 能否切换到下一页 ***/ @property(nonatomic) BOOL scrollToNextEnabled; @end @implementation TFCoveragePageTurningController - (void)dealloc { // 移除手势 [self.view removeGestureRecognizer:self.pan]; [self.view removeGestureRecognizer:self.tap]; // 移除当前控制器 if (self.currentController) { [self.currentController.view removeFromSuperview]; [self.currentController removeFromParentViewController]; _currentController = nil; } // 移除临时控制器 if (self.tempController) { [self.tempController.view removeFromSuperview]; [self.tempController removeFromParentViewController]; self.tempController = nil; } if (self.currentView) { [self.currentView removeFromSuperview]; _currentView = nil; } if (self.tempView) { [self.tempView removeFromSuperview]; self.tempView = nil; } } - (instancetype)init { if (self = [super init]) { self.contentModeController = YES; self.switchAnimated = YES; self.pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)]; self.tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGestureRecognizer:)]; self.switchSlideEnabled = YES; self.switchTapEnabled = YES; self.switchToLastEnabled = YES; self.switchToNextEnabled = YES; self.circleSwitchEnabled = YES; self.scrollToLastEnabled = YES; self.scrollToNextEnabled = YES; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.view.layer.masksToBounds = YES; [self.view removeGestureRecognizer:self.tap]; [self.view removeGestureRecognizer:self.pan]; [self.view addGestureRecognizer:self.pan]; [self.view addGestureRecognizer:self.tap]; } - (void)handlePanGestureRecognizer:(UIPanGestureRecognizer *)recognizer { CGPoint tempPoint = [recognizer translationInView:self.view]; if (recognizer.state == UIGestureRecognizerStateBegan) { if (self.isAnimating) return; self.isAnimating = YES; self.isPan = YES; self.isPanBegin = YES; } else if (recognizer.state == UIGestureRecognizerStateChanged) { if (self.isPanBegin) { self.isPanBegin = NO; self.isLeft = tempPoint.x > 0 ? YES: NO; if (![self canHandlePanOrTap]) { self.isPan = NO; return; } if (self.contentModeController) { self.tempController = [self getNeedsController]; [self addController:self.tempController]; } else { self.tempView = [self getNeedsView]; [self addView:self.tempView]; } } if (!self.isPan) return; if (self.contentModeController) { if (!self.tempController) return; if (self.isLeft) { self.tempController.view.frame = CGRectMake(tempPoint.x - self.view.width ,0 ,self.view.width ,self.view.height); } else { self.currentController.view.frame = CGRectMake(tempPoint.x > 0 ? 0 : tempPoint.x ,0 ,self.view.width ,self.view.height); } } else { if (!self.tempView) return; if (self.switchAnimated) { if (self.isLeft) { self.tempView.frame = CGRectMake(tempPoint.x - self.view.width ,0 ,self.view.width ,self.view.height); } else { self.currentView.frame = CGRectMake(tempPoint.x > 0 ? 0 : tempPoint.x ,0 ,self.view.width ,self.view.height); } } } } else { //!< 手势结束 if (!self.isPan) return; self.isPan = NO; if (self.contentModeController) { if (!self.tempController) { self.isAnimating = NO; return; } BOOL isSuccess = YES; if (self.isLeft) { if (self.tempController.view.frame.origin.x <= -(self.view.width - self.view.width*0.18)) { isSuccess = NO; } } else { if (self.currentController.view.frame.origin.x >= -self.view.width*0.18) { isSuccess = NO; } } [self GestureSuccess:isSuccess animated:self.switchAnimated]; } else { if (!self.tempView) { self.isAnimating = NO; return; } BOOL isSuccess = YES; if (self.switchAnimated) { if (self.isLeft) { if (self.tempView.frame.origin.x <= -(self.view.width - self.view.width*0.18)) { isSuccess = NO; } } else { if (self.currentView.frame.origin.x >= -self.view.width*0.18) { isSuccess = NO; } } } // 手势结束 [self GestureSuccess:isSuccess animated:self.switchAnimated]; } } } - (void)handleTapGestureRecognizer:(UITapGestureRecognizer *)recognizer { if (self.isAnimating) return; self.isAnimating = YES; CGPoint touchPoint = [recognizer locationInView:self.view]; self.isLeft = touchPoint.x < self.view.centerX ? YES: NO; if (![self canHandlePanOrTap]) { return; } if (self.contentModeController) { self.tempController = [self getNeedsController]; [self addController:self.tempController]; } else { self.tempView = [self getNeedsView]; [self addView:self.tempView]; } [self GestureSuccess:YES animated:self.switchAnimated]; } - (BOOL)canHandlePanOrTap { BOOL can = YES; if (!self.numberOfPages) { self.isAnimating = NO; return NO; } if (self.isLeft) { if (!self.switchToLastEnabled || !self.scrollToLastEnabled) { self.isAnimating = NO; can = NO; if ([self.delegate respondsToSelector:@selector(pageControllerSwitchToLastDisabled:)]) { [self.delegate pageControllerSwitchToLastDisabled:self]; } } } else { if (!self.switchToNextEnabled || !self.scrollToNextEnabled) { self.isAnimating = NO; can = NO; if ([self.delegate respondsToSelector:@selector(pageControllerSwitchToNextDisabled:)]) { [self.delegate pageControllerSwitchToNextDisabled:self]; } } } return can; } - (UIViewController *)getNeedsController { UIViewController *vc = nil; if (![self.dataSource respondsToSelector:@selector(pageController:controllerForIndex:)]) { self.isAnimating = NO; return vc; } if (self.isLeft) { vc = [self.dataSource pageController:self controllerForIndex:self.lastIndex]; } else { vc = [self.dataSource pageController:self controllerForIndex:self.nextIndex]; } if (!vc) { self.isAnimating = NO; } return vc; } - (UIView *)getNeedsView { UIView *view = nil; if (![self.dataSource respondsToSelector:@selector(pageController:viewForIndex:direction:)]) { self.isAnimating = NO; return view; } if (self.isLeft) { view = [self.dataSource pageController:self viewForIndex:self.lastIndex direction:TFPageRollingDirectionLeft]; } else { view = [self.dataSource pageController:self viewForIndex:self.nextIndex direction:TFPageRollingDirectionRight]; } if (!view) { self.isAnimating = NO; } return view; } // 手势结束 - (void)GestureSuccess:(BOOL)isSuccess animated:(BOOL)animated { if (self.contentModeController) { if (self.tempController) { if (self.isLeft) { // 左边 if (animated) { __weak TFCoveragePageTurningController *weakSelf = self; [UIView animateWithDuration:AnimateDuration animations:^{ if (isSuccess) { weakSelf.tempController.view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } else { weakSelf.tempController.view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } } completion:^(BOOL finished) { [weakSelf animateSuccess:isSuccess]; }]; } else { if (isSuccess) { self.tempController.view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } else { self.tempController.view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } [self animateSuccess:isSuccess]; } } else { // 右边 if (animated) { __weak TFCoveragePageTurningController *weakSelf = self; [UIView animateWithDuration:AnimateDuration animations:^{ if (isSuccess) { weakSelf.currentController.view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } else { weakSelf.currentController.view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } } completion:^(BOOL finished) { [weakSelf animateSuccess:isSuccess]; }]; } else { if (isSuccess) { self.currentController.view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } else { self.currentController.view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } [self animateSuccess:isSuccess]; } } } } else { if (self.tempView) { if (self.isLeft) { // 左边 if (animated) { __weak TFCoveragePageTurningController *weakSelf = self; [UIView animateWithDuration:AnimateDuration animations:^{ if (isSuccess) { weakSelf.tempView.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } else { weakSelf.tempView.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } } completion:^(BOOL finished) { [weakSelf animateSuccess:isSuccess]; }]; } else { if (isSuccess) { self.tempView.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } else { self.tempView.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } [self animateSuccess:isSuccess]; } } else { // 右边 if (animated) { __weak TFCoveragePageTurningController *weakSelf = self; [UIView animateWithDuration:AnimateDuration animations:^{ if (isSuccess) { weakSelf.currentView.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } else { weakSelf.currentView.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } } completion:^(BOOL finished) { [weakSelf animateSuccess:isSuccess]; }]; } else { if (isSuccess) { self.currentView.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } else { self.currentView.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } [self animateSuccess:isSuccess]; } } } } } // 动画结束 - (void)animateSuccess:(BOOL)isSuccess { if (self.contentModeController) { if (isSuccess) { [self.currentController.view removeFromSuperview]; [self.currentController removeFromParentViewController]; _currentController = self.tempController; self.tempController = nil; self.isAnimating = NO; if (self.isLeft) { self.currentIndex --; } else { self.currentIndex ++; } } else { [self.tempController.view removeFromSuperview]; [self.tempController removeFromParentViewController]; self.tempController = nil; self.isAnimating = NO; if (self.isLeft) { [self.dataSource pageController:self viewForIndex:self.lastIndex direction:TFPageRollingDirectionRight]; } else { [self.dataSource pageController:self viewForIndex:self.nextIndex direction:TFPageRollingDirectionLeft]; } } // 代理回调 if ([self.delegate respondsToSelector:@selector(pageController:currentController:currentIndex:)]) { [self.delegate pageController:self currentController:self.currentController currentIndex:self.currentIndex]; } } else { if (isSuccess) { [self.currentView removeFromSuperview]; _currentView = self.tempView; self.tempView = nil; self.isAnimating = NO; if (self.isLeft) { self.currentIndex --; } else { self.currentIndex ++; } } else { [self.tempView removeFromSuperview]; self.tempView = nil; self.isAnimating = NO; } if ([self.delegate respondsToSelector:@selector(pageController:currentView:currentIndex:direction:)]) { [self.delegate pageController:self currentView:self.currentView currentIndex:self.currentIndex direction:self.isLeft ? TFPageRollingDirectionRight : TFPageRollingDirectionLeft]; } } } - (void)addController:(UIViewController * _Nullable)controller { if (controller) { [self addChildViewController:controller]; if (self.isLeft) { // 左边 [self.view addSubview:controller.view]; controller.view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } else { // 右边 if (self.currentController) { // 有值 [self.view insertSubview:controller.view belowSubview:self.currentController.view]; } else { // 没值 [self.view addSubview:controller.view]; } controller.view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } if (self.switchAnimated) { // 添加阴影 [self setShadowController:controller]; } } } #pragma mark - 设置显示View - (void)addView:(UIView * _Nullable)view { if (view) { if (self.isLeft) { // 左边 [self.view addSubview:view]; view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } else { // 右边 if (self.currentView) { [self.view insertSubview:view belowSubview:self.currentView]; } else { [self.view addSubview:view]; } view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } if (self.switchAnimated) { [self setShadowView:view]; } } } // 给控制器添加阴影 - (void)setShadowController:(UIViewController * _Nullable)controller { if (controller) { controller.view.layer.shadowColor = [UIColor blackColor].CGColor; // 阴影颜色 controller.view.layer.shadowOffset = CGSizeMake(0 ,0); // 偏移距离 controller.view.layer.shadowOpacity = 0.5; // 不透明度 controller.view.layer.shadowRadius = 10.0; // 半径 } } // 给 View 添加阴影 - (void)setShadowView:(UIView * _Nullable)view { if (view) { view.layer.shadowColor = [UIColor blackColor].CGColor; // 阴影颜色 view.layer.shadowOffset = CGSizeMake(0 ,0); // 偏移距离 view.layer.shadowOpacity = 0.5; // 不透明度 view.layer.shadowRadius = 10.0; // 半径 } } - (void)reloadData { if ([self.dataSource respondsToSelector:@selector(numberOfPagesInPageController:)]) { _numberOfPages = [self.dataSource numberOfPagesInPageController:self]; if (_numberOfPages <= 0) return; if (self.currentIndex && self.currentIndex < self.numberOfPages) { self.currentIndex = self.currentIndex; } self.currentIndex = 0; if (self.contentModeController) { if ([self.dataSource respondsToSelector:@selector(pageController:controllerForIndex:)]) { self.tempController = [self.dataSource pageController:self controllerForIndex:self.currentIndex]; [self initViewController]; } } else { if ([self.dataSource respondsToSelector:@selector(pageController:viewForIndex:direction:)]) { self.tempView = [self.dataSource pageController:self viewForIndex:self.currentIndex direction:TFPageRollingDirectionNone]; [self initView]; } } } } - (void)reloadDataToFirst { if ([self.dataSource respondsToSelector:@selector(numberOfPagesInPageController:)]) { _numberOfPages = [self.dataSource numberOfPagesInPageController:self]; if (_numberOfPages <= 0) return; self.currentIndex = 0; if (self.contentModeController) { if ([self.dataSource respondsToSelector:@selector(pageController:controllerForIndex:)]) { self.tempController = [self.dataSource pageController:self controllerForIndex:self.currentIndex]; [self initViewController]; } } else { if ([self.dataSource respondsToSelector:@selector(pageController:viewForIndex:direction:)]) { self.tempView = [self.dataSource pageController:self viewForIndex:self.currentIndex direction:TFPageRollingDirectionNone]; [self initView]; } } } } - (void)initViewController { [self addController:self.tempController]; if (self.isAnimating) { __weak TFCoveragePageTurningController *weakSelf = self; if (self.isLeft) { [UIView animateWithDuration:AnimateDuration animations:^{ weakSelf.tempController.view.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } completion:^(BOOL finished) { [weakSelf initViewControllerComplented]; }]; } else { [UIView animateWithDuration:AnimateDuration animations:^{ weakSelf.currentController.view.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } completion:^(BOOL finished) { [weakSelf initViewControllerComplented]; }]; } return; } self.tempController.view.frame = self.view.bounds; [self initViewControllerComplented]; } - (void)initViewControllerComplented { self.isAnimating = NO; if (_currentController) { [_currentController.view removeFromSuperview]; [_currentController removeFromParentViewController]; _currentController = nil; } _currentController = self.tempController; self.tempController = nil; if ([self.delegate respondsToSelector:@selector(pageController:currentController:currentIndex:)]) { [self.delegate pageController:self currentController:self.currentController currentIndex:self.currentIndex]; } } - (void)initView { [self addView:self.tempView]; if (self.isAnimating) { __weak TFCoveragePageTurningController *weakSelf = self; if (self.isLeft) { [UIView animateWithDuration:AnimateDuration animations:^{ weakSelf.tempView.frame = CGRectMake(0 ,0 ,self.view.width ,self.view.height); } completion:^(BOOL finished) { [weakSelf initViewComplented]; }]; } else { [UIView animateWithDuration:AnimateDuration animations:^{ weakSelf.currentView.frame = CGRectMake(-self.view.width ,0 ,self.view.width ,self.view.height); } completion:^(BOOL finished) { [weakSelf initViewComplented]; }]; } return; } self.tempView.frame = self.view.bounds; [self initViewComplented]; } - (void)initViewComplented { self.isAnimating = NO; if (_currentView) { [_currentView removeFromSuperview]; _currentView = nil; } _currentView = self.tempView; self.tempView = nil; if ([self.delegate respondsToSelector:@selector(pageController:currentView:currentIndex:direction:)]) { [self.delegate pageController:self currentView:self.currentView currentIndex:self.currentIndex direction:TFPageRollingDirectionNone]; } } - (BOOL)canSwitchToIndex:(NSInteger)index { if (index < 0) { return NO; } if (index > self.numberOfPages - 1) { return NO; } return YES; } - (void)switchToIndex:(NSInteger)index animated:(BOOL)animated { if (self.currentIndex == index) { return; } self.isAnimating = animated; self.isLeft = index < self.currentIndex ? YES: NO; self.currentIndex = index; if (self.contentModeController) { if ([self.dataSource pageController:self controllerForIndex:self.currentIndex]) { self.tempController = [self.dataSource pageController:self controllerForIndex:self.currentIndex]; [self initViewController]; } } else { if ([self.dataSource respondsToSelector:@selector(pageController:viewForIndex:direction:)]) { self.tempView = [self.dataSource pageController:self viewForIndex:self.currentIndex direction:TFPageRollingDirectionRight]; [self initView]; } } } #pragma mark - getter/setter - (void)setCurrentIndex:(NSInteger)currentIndex { if (self.circleSwitchEnabled) { if (currentIndex == -1) { currentIndex = self.numberOfPages - 1; } else if (currentIndex == self.numberOfPages) { currentIndex = 0; } } else { self.scrollToNextEnabled = self.scrollToLastEnabled = YES; if (currentIndex == 0) { self.scrollToLastEnabled = NO; } if (currentIndex == self.numberOfPages - 1) { self.scrollToNextEnabled = NO; } } _currentIndex = currentIndex; if (currentIndex == 0) { if ([self.delegate respondsToSelector:@selector(pageControllerDidSwitchToFirst:)]) { [self.delegate pageControllerDidSwitchToFirst:self]; } } if (currentIndex == self.numberOfPages - 1) { if ([self.delegate respondsToSelector:@selector(pageControllerDidSwitchToLast:)]) { [self.delegate pageControllerDidSwitchToLast:self]; } } } - (NSInteger)lastIndex { NSInteger lastIndex = self.currentIndex - 1; if (!self.circleSwitchEnabled) return lastIndex; if (lastIndex < 0) { lastIndex = self.numberOfPages - 1; } return lastIndex; } - (NSInteger)nextIndex { NSInteger nextIndex = self.currentIndex + 1; if (!self.circleSwitchEnabled) return nextIndex; if (nextIndex >= self.numberOfPages) { nextIndex = 0; } return nextIndex; } - (void)setSwitchTapEnabled:(BOOL)switchTapEnabled { _switchTapEnabled = switchTapEnabled; self.tap.enabled = switchTapEnabled; } - (void)setSwitchSlideEnabled:(BOOL)switchSlideEnabled { _switchSlideEnabled = switchSlideEnabled; self.pan.enabled = switchSlideEnabled; } @end