123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- //
- // XLPageViewController.m
- // XLPageViewControllerExample
- //
- // Created by MengXianLiang on 2019/5/6.
- // Copyright © 2019 xianliang meng. All rights reserved.
- // https://github.com/mengxianliang/XLPageViewController
- #import "XLPageViewController.h"
- #import "XLPageBasicTitleView.h"
- #import "XLPageSegmentedTitleView.h"
- #import "WFCUUtilities.h"
- //调用setViewControllers方法时,可能会用到延时
- static float SetViewControllersMethodDelay = 0.1;
- typedef void(^XLContentScollBlock)(BOOL scrollEnabled);
- @interface XLPageContentView : UIView
- @property (nonatomic, strong) XLContentScollBlock scrollBlock;
- @end
- @implementation XLPageContentView
- //兼容和子view滚动冲突问题
- - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
- UIView *view = [super hitTest:point withEvent:event];
- BOOL pageViewScrollEnabled = !view.xl_letMeScrollFirst;
- if (self.scrollBlock) {
- self.scrollBlock(pageViewScrollEnabled);
- }
- return view;
- }
- @end
- @interface XLPageViewController ()<UIPageViewControllerDelegate, UIPageViewControllerDataSource,UIScrollViewDelegate,XLPageTitleViewDataSrouce,XLPageTitleViewDelegate>
- //所有的子视图,都加载在contentView上
- @property (nonatomic, strong) XLPageContentView *contentView;
- //标题
- @property (nonatomic, strong) XLPageBasicTitleView *titleView;
- //分页控制器
- @property (nonatomic, strong) UIPageViewController *pageVC;
- //ScrollView
- @property (nonatomic, strong) UIScrollView *scrollView;
- //显示过的vc数组,用于试图控制器缓存
- @property (nonatomic, strong) NSMutableArray *shownVCArr;
- //所有标题集合
- @property (nonatomic, strong) NSMutableArray *allTitleArr;
- //是否加载了pageVC
- @property (nonatomic, assign) BOOL pageVCDidLoad;
- //判断pageVC是否在切换中
- @property (nonatomic, assign) BOOL pageVCAnimating;
- //当前配置信息
- @property (nonatomic, strong) XLPageViewControllerConfig *config;
- //上一次代理返回的index
- @property (nonatomic, assign) NSInteger lastDelegateIndex;
- //手指拖拽距离
- @property (nonatomic, assign) CGFloat dragStartX;
- @end
- @implementation XLPageViewController
- #pragma mark -
- #pragma mark 初始化方法
- //初始化需要使用initWithConfig方法
- - (instancetype)init {
- if (self = [super init]) {
- [NSException raise:@"Do not use this method" format:@"Must be initialized by initWithConfig"];
- }
- return self;
- }
- - (instancetype)initWithConfig:(XLPageViewControllerConfig *)config {
- if (self = [super init]) {
- [self initUIWithConfig:config];
- [self initData];
- }
- return self;
- }
- - (void)initUIWithConfig:(XLPageViewControllerConfig *)config {
-
- //保存配置
- self.config = config;
-
- //创建contentview
- self.contentView = [[XLPageContentView alloc] init];
- [self.view addSubview:self.contentView];
- __weak typeof(self)weakSelf = self;
- self.contentView.scrollBlock = ^(BOOL scrollEnabled) {
- if (weakSelf.scrollEnabled) {
- weakSelf.scrollView.scrollEnabled = scrollEnabled;
- }
- };
-
- //防止Navigation引起的缩进
- UIView *topView = [[UIView alloc] init];
- [self.contentView addSubview:topView];
-
- //创建标题
- self.titleView = [[XLPageBasicTitleView alloc] initWithConfig:config];
- if (config.titleViewStyle == XLPageTitleViewStyleSegmented) {
- self.titleView = [[XLPageSegmentedTitleView alloc] initWithConfig:config];
- }
- self.titleView.dataSource = self;
- self.titleView.delegate = self;
- [self.contentView addSubview:self.titleView];
-
- //创建PageVC
- self.pageVC = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
- self.pageVC.delegate = self;
- self.pageVC.dataSource = self;
- [self.contentView addSubview:self.pageVC.view];
- [self addChildViewController:self.pageVC];
-
- //设置ScrollView代理
- for (UIScrollView *scrollView in self.pageVC.view.subviews) {
- if ([scrollView isKindOfClass:[UIScrollView class]]) {
- self.scrollView = scrollView;
- self.scrollView.delegate = self;
- }
- }
-
- //默认可以滚动
- self.scrollEnabled = YES;
-
- //默认打开自动回答弹
- self.bounces = YES;
-
- //初始化上一次返回的index
- self.lastDelegateIndex = -1;
-
- //兼容全屏返回手势识别
- self.scrollView.xl_otherGestureRecognizerBlock = ^BOOL(UIGestureRecognizer * _Nonnull otherGestureRecognizer) {
- return weakSelf.selectedIndex == 0 && [weakSelf.respondOtherGestureDelegateClassList containsObject:NSStringFromClass(otherGestureRecognizer.delegate.class)];
- };
- }
- //初始化vc缓存数组
- - (void)initData {
- self.shownVCArr = [[NSMutableArray alloc] init];
- }
- //设置titleView位置
- - (void)viewWillAppear:(BOOL)animated {
- [super viewWillAppear:animated];
- if (self.config.showTitleInNavigationBar) {
- self.parentViewController.navigationItem.titleView = self.titleView;
- }
- }
- - (void)viewDidLayoutSubviews {
- [super viewDidLayoutSubviews];
-
- //更新contentview位置
- self.contentView.frame = self.view.bounds;
-
- //更新标题位置
- self.titleView.frame = CGRectMake(0, 0, self.contentView.bounds.size.width, self.config.titleViewHeight);
-
- //更新pageVC位置
- self.pageVC.view.frame = CGRectMake(0, self.config.titleViewHeight, self.contentView.bounds.size.width, self.contentView.bounds.size.height - self.config.titleViewHeight);
-
- if (self.config.showTitleInNavigationBar) {
- self.pageVC.view.frame = self.contentView.bounds;
- }
-
- //自动选中当前位置_selectedIndex
- if (!self.pageVCDidLoad) {
- //设置加载标记为已加载
- [self switchToViewControllerAdIndex:_selectedIndex animated:NO];
- self.pageVCDidLoad = YES;
- }
-
- //初始化标题数组
- self.allTitleArr = [[NSMutableArray alloc] init];
- for (NSInteger i = 0; i < [self numberOfPage]; i++) {
- [self.allTitleArr addObject:[self titleForIndex:i]];
- }
- }
- #pragma mark -
- #pragma mark UIPageViewControllerDelegate
- //滚动切换时调用
- - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
- self.pageVCAnimating = YES;
- }
- //滚动切换时调用
- - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
- if (!completed) {
- //切换中属性更新
- self.pageVCAnimating = NO;
- return;
- }
- //更新选中位置
- UIViewController *vc = self.pageVC.viewControllers.firstObject;
- _selectedIndex = [self.allTitleArr indexOfObject:vc.xl_title];
- //标题居中
- self.titleView.selectedIndex = _selectedIndex;
- //回调代理方法
- [self delegateSelectedAdIndex:_selectedIndex];
- //切换中属性更新
- self.pageVCAnimating = NO;
- }
- #pragma mark -
- #pragma mark UIPageViewControllerDataSource
- - (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
- //修正因为拖拽距离过大,导致空白界面问题
- [self fixSelectedIndexWhenDragingBefore];
- return [self viewControllerForIndex:_selectedIndex - 1];
- }
- - (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
- //修正因为拖拽距离过大,导致空白界面问题
- [self fixSelectedIndexWhenDragingAfter];
- return [self viewControllerForIndex:_selectedIndex + 1];
- }
- //修正因拖拽距离过大,导致出现空白界面问题
- - (void)fixSelectedIndexWhenDragingBefore {
- //计算手动拖拽的距离,避免快速滑动时触发以下算法
- CGFloat dragDistance = fabs(self.scrollView.contentOffset.x - self.dragStartX);
- //判断1、是否是手指正在拖拽 2、是否滑动距离够大。如果两者成立,则表示需要手动修正位置
- if (self.scrollView.isTracking && dragDistance > self.scrollView.bounds.size.width) {
- self.pageVCAnimating = NO;
- NSInteger targetIndex = _selectedIndex - 1;
- targetIndex = targetIndex < 0 ? 0 : targetIndex;
- self.selectedIndex = targetIndex;
- self.titleView.stopAnimation = NO;
- //执行代理方法
- [self delegateSelectedAdIndex:targetIndex];
- }
- }
- //修正因拖拽距离过大,导致出现空白界面问题
- - (void)fixSelectedIndexWhenDragingAfter {
- //计算手动拖拽的距离,避免快速滑动时触发以下算法
- CGFloat dragDistance = fabs(self.scrollView.contentOffset.x - self.dragStartX);
- //判断1、是否是手指正在拖拽 2、是否滑动距离够大。如果两者成立,则表示需要手动修正位置
- if (self.scrollView.isTracking && dragDistance > self.scrollView.bounds.size.width) {
- self.pageVCAnimating = NO;
- NSInteger targetIndex = _selectedIndex + 1;
- targetIndex = targetIndex >= [self numberOfPage] ? [self numberOfPage] - 1 : targetIndex;
- self.selectedIndex = targetIndex;
- self.titleView.stopAnimation = NO;
- //执行代理方法
- [self delegateSelectedAdIndex:targetIndex];
- }
- }
- #pragma mark -
- #pragma mark ScrollViewDelegate
- //滚动时计算标题动画进度
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
- CGFloat value = scrollView.contentOffset.x - scrollView.bounds.size.width;
- self.titleView.animationProgress = value/scrollView.bounds.size.width;
-
- //设置拖拽手势问题
- if (self.respondOtherGestureDelegateClassList.count > 0) {
- BOOL scrollDisababled = value < 0 && self.selectedIndex == 0 && self.respondOtherGestureDelegateClassList.count;
- scrollView.scrollEnabled = !scrollDisababled;
- }
-
- //设置边缘回弹问题
- BOOL dragToTheEdge = (self.selectedIndex == 0 && value < 0) || (self.selectedIndex == [self numberOfPage] - 1 && value > 0);
- if (dragToTheEdge && scrollView.isDragging && !self.bounces) {
- scrollView.scrollEnabled = NO;
- }
- }
- //更新执行动画状态
- - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
- self.titleView.stopAnimation = false;
- }
- //更新执行动画状态
- - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
- self.titleView.stopAnimation = false;
- }
- //更新执行动画状态
- - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
- self.titleView.stopAnimation = false;
- //保存手指拖拽起始位置
- self.dragStartX = scrollView.contentOffset.x;
- }
- //更新执行动画状态
- - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
- self.titleView.stopAnimation = false;
- scrollView.scrollEnabled = self.scrollEnabled;
- }
- #pragma mark -
- #pragma mark PageTitleViewDataSource&Delegate
- //titleview数据源方法
- - (NSInteger)pageTitleViewNumberOfTitle {
- return [self numberOfPage];
- }
- //titleview数据源方法
- - (NSString *)pageTitleViewTitleForIndex:(NSInteger)index {
- return [self titleForIndex:index];
- }
- //titleview数据源方法
- - (XLPageTitleCell *)pageTitleViewCellForItemAtIndex:(NSInteger)index {
- if ([self.dataSource respondsToSelector:@selector(pageViewController:titleViewCellForItemAtIndex:)]) {
- return [self.dataSource pageViewController:self titleViewCellForItemAtIndex:index];
- }
- return nil;
- }
- //titleview代理方法
- - (BOOL)pageTitleViewDidSelectedAtIndex:(NSInteger)index {
- BOOL switchSuccess = [self switchToViewControllerAdIndex:index animated:YES];
- if (!switchSuccess) {
- return false;
- }
- self.titleView.stopAnimation = true;
- //点击标题切换时会延时加载,所以要延时回调代理方法,避免出现获取当前VC不正确问题
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(SetViewControllersMethodDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [self delegateSelectedAdIndex:index];
- });
- return true;
- }
- #pragma mark -
- #pragma mark Setter
- //设置选中位置
- - (void)setSelectedIndex:(NSInteger)selectedIndex {
- //范围越界,抛出异常
- if (selectedIndex < 0 || selectedIndex > [self numberOfPage]) {
- [NSException raise:@"selectedIndex is not right !!!" format:@"It is out of range"];
- }
- BOOL switchSuccess = [self switchToViewControllerAdIndex:selectedIndex animated:YES];
- if (!switchSuccess) {return;}
- self.titleView.stopAnimation = true;
- //更新代理反馈的index
- self.lastDelegateIndex = selectedIndex;
- }
- //滑动开关
- - (void)setScrollEnabled:(BOOL)scrollEnabled {
- _scrollEnabled = scrollEnabled;
- self.scrollView.scrollEnabled = scrollEnabled;
- }
- //设置右侧按钮
- - (void)setRightButton:(UIButton *)rightButton {
- _titleView.rightButton = rightButton;
- }
- #pragma mark -
- #pragma mark 切换位置方法
- - (BOOL)switchToViewControllerAdIndex:(NSInteger)index animated:(BOOL)animated {
- if ([self numberOfPage] == 0) {return NO;}
- //如果正在加载中 返回
- if (self.pageVCAnimating && self.config.titleViewStyle == XLPageTitleViewStyleBasic) {return NO;}
- if (index == _selectedIndex && index >= 0 && self.pageVCDidLoad) {return NO;}
- //设置正在加载标记
- self.pageVCAnimating = animated;
- //更新当前位置
- _selectedIndex = index;
- //设置滚动方向
- UIPageViewControllerNavigationDirection direction = UIPageViewControllerNavigationDirectionForward;
- if (_titleView.lastSelectedIndex > _selectedIndex) {
- direction = UIPageViewControllerNavigationDirectionReverse;
- }
- //设置当前展示VC
- __weak typeof(self)weakSelf = self;
- [self.pageVC setViewControllers:@[[self viewControllerForIndex:index]] direction:direction animated:NO completion:^(BOOL finished) {
- weakSelf.pageVCAnimating = NO;
- }];
-
- //延时是为了避免切换视图时有其它操作阻塞UI
- self.view.userInteractionEnabled = NO;
- float delayTime = animated ? SetViewControllersMethodDelay : 0;
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- weakSelf.view.userInteractionEnabled = YES;
- });
-
- //标题居中
- self.titleView.selectedIndex = _selectedIndex;
- return YES;
- }
- #pragma mark -
- #pragma mark 刷新方法
- - (void)reloadData {
- self.pageVCDidLoad = NO;
- //刷新标题栏UI
- [self.titleView reloadData];
- //刷新标题数据
- [self.allTitleArr removeAllObjects];
- for (NSInteger i = 0; i < [self numberOfPage]; i++) {
- [self.allTitleArr addObject:[self titleForIndex:i]];
- }
- }
- #pragma mark -
- #pragma mark 自定义方法
- - (void)registerClass:(Class)cellClass forTitleViewCellWithReuseIdentifier:(NSString *)identifier {
- [self.titleView registerClass:cellClass forTitleViewCellWithReuseIdentifier:identifier];
- }
- - (XLPageTitleCell *)dequeueReusableTitleViewCellWithIdentifier:(NSString *)identifier forIndex:(NSInteger)index {
- return [self.titleView dequeueReusableCellWithIdentifier:identifier forIndex:index];
- }
- #pragma mark -
- #pragma mark 辅助方法
- //指定位置的视图控制器 缓存方法
- - (UIViewController *)viewControllerForIndex:(NSInteger)index {
-
- //如果越界,则返回nil
- if (index < 0 || index >= [self numberOfPage]) {
- return nil;
- }
- //获取当前vc
- UIViewController *currentVC = self.pageVC.viewControllers.firstObject;
- //当前标题
- NSString *currentTitle = currentVC.xl_title;
- //目标切换位置标题
- NSString *targetTitle = [self titleForIndex:index];
- //如果和当前位置一样,则返回当前vc
- if ([currentTitle isEqualToString:targetTitle]) {
- return currentVC;
- }
-
- //如果之前显示过,则从内存中读取
- for (UIViewController *vc in self.shownVCArr) {
- if ([vc.xl_title isEqualToString:targetTitle]) {
- return vc;
- }
- }
-
- //如果之前没显示过,则通过dataSource创建
- UIViewController *vc = [self.dataSource pageViewController:self viewControllerForIndex:index];
- //设置扩展id,用户定位对应视图控制器
- vc.xl_title = [self titleForIndex:index];
- //设置试图控制器标题
- vc.title = [self titleForIndex:index];
- //把vc添加到缓存集合
- [self.shownVCArr addObject:vc];
- //添加子视图控制器
- [self addChildViewController:vc];
- return vc;
- }
- //指定位置的标题
- - (NSString *)titleForIndex:(NSInteger)index {
- return [self.dataSource pageViewController:self titleForIndex:index];
- }
- //总页数
- - (NSInteger)numberOfPage {
- return [self.dataSource pageViewControllerNumberOfPage];
- }
- //执行代理方法
- - (void)delegateSelectedAdIndex:(NSInteger)index {
- if (index == self.lastDelegateIndex) {return;}
- self.lastDelegateIndex = index;
- if ([self.delegate respondsToSelector:@selector(pageViewController:didSelectedAtIndex:)]) {
- [self.delegate pageViewController:self didSelectedAtIndex:index];
- }
- }
- @end
|