酷店

XLPageViewController.m 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. //
  2. // XLPageViewController.m
  3. // XLPageViewControllerExample
  4. //
  5. // Created by MengXianLiang on 2019/5/6.
  6. // Copyright © 2019 jwzt. All rights reserved.
  7. // https://github.com/mengxianliang/XLPageViewController
  8. #import "XLPageViewController.h"
  9. #import "XLPageBasicTitleView.h"
  10. #import "XLPageSegmentedTitleView.h"
  11. typedef void(^XLContentScollBlock)(BOOL scrollEnabled);
  12. @interface XLPageContentView : UIView
  13. @property (nonatomic, strong) XLContentScollBlock scrollBlock;
  14. @end
  15. @implementation XLPageContentView
  16. //兼容和子view滚动冲突问题
  17. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  18. UIView *view = [super hitTest:point withEvent:event];
  19. BOOL pageViewScrollEnabled = !view.xl_letMeScrollFirst;
  20. self.scrollBlock(pageViewScrollEnabled);
  21. return view;
  22. }
  23. @end
  24. typedef NS_ENUM(NSInteger,XLScrollDirection) {
  25. XLScrollDirectionNone = 0,
  26. XLScrollDirectionLeft = 1,
  27. XLScrollDirectionRight = 2,
  28. };
  29. @interface XLPageViewController ()<UIPageViewControllerDelegate, UIPageViewControllerDataSource,UIScrollViewDelegate,XLPageTitleViewDataSrouce,XLPageTitleViewDelegate>
  30. //所有的子视图,都加载在contentView上
  31. @property (nonatomic, strong) XLPageContentView *contentView;
  32. //标题
  33. @property (nonatomic, strong) XLPageBasicTitleView *titleView;
  34. //分页控制器
  35. @property (nonatomic, strong) UIPageViewController *pageVC;
  36. //显示过的vc数组,用于试图控制器缓存
  37. @property (nonatomic, strong) NSMutableArray *shownVCArr;
  38. //是否加载了pageVC
  39. @property (nonatomic, assign) BOOL pageVCDidLoad;
  40. //判断pageVC是否在切换中
  41. @property (nonatomic, assign) BOOL pageVCAnimating;
  42. //滚动方向
  43. @property (nonatomic, assign) XLScrollDirection scrollDirection;
  44. //当前配置信息
  45. @property (nonatomic, strong) XLPageViewControllerConfig *config;
  46. //上一次代理返回的index
  47. @property (nonatomic, assign) NSInteger lastDelegateIndex;
  48. @end
  49. @implementation XLPageViewController
  50. #pragma mark -
  51. #pragma mark 初始化方法
  52. - (instancetype)initWithConfig:(XLPageViewControllerConfig *)config {
  53. if (self = [super init]) {
  54. [self initUIWithConfig:config];
  55. [self initData];
  56. }
  57. return self;
  58. }
  59. - (void)initUIWithConfig:(XLPageViewControllerConfig *)config {
  60. //保存配置
  61. self.config = config;
  62. //创建contentview
  63. self.contentView = [[XLPageContentView alloc] init];
  64. [self.view addSubview:self.contentView];
  65. __weak typeof(self)weakSelf = self;
  66. self.contentView.scrollBlock = ^(BOOL scrollEnabled) {
  67. weakSelf.scrollEnabled = scrollEnabled;
  68. };
  69. //防止Navigation引起的缩进
  70. UIView *topView = [[UIView alloc] init];
  71. [self.contentView addSubview:topView];
  72. //创建标题
  73. self.titleView = [[XLPageBasicTitleView alloc] initWithConfig:config];
  74. if (config.titleViewStyle == XLPageTitleViewStyleSegmented) {
  75. self.titleView = [[XLPageSegmentedTitleView alloc] initWithConfig:config];
  76. }
  77. self.titleView.dataSource = self;
  78. self.titleView.delegate = self;
  79. [self.contentView addSubview:self.titleView];
  80. //创建PageVC
  81. self.pageVC = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
  82. self.pageVC.delegate = self;
  83. self.pageVC.dataSource = self;
  84. [self.contentView addSubview:self.pageVC.view];
  85. [self addChildViewController:self.pageVC];
  86. //设置ScrollView代理
  87. for (UIScrollView *scrollView in self.pageVC.view.subviews) {
  88. if ([scrollView isKindOfClass:[UIScrollView class]]) {
  89. scrollView.delegate = self;
  90. }
  91. }
  92. //默认可以滚动
  93. self.scrollEnabled = YES;
  94. //初始化上一次返回的index
  95. self.lastDelegateIndex = -1;
  96. }
  97. //初始化vc缓存数组
  98. - (void)initData {
  99. self.shownVCArr = [[NSMutableArray alloc] init];
  100. }
  101. //设置titleView位置
  102. - (void)viewWillAppear:(BOOL)animated {
  103. [super viewWillAppear:animated];
  104. if (self.config.showTitleInNavigationBar) {
  105. self.parentViewController.navigationItem.titleView = self.titleView;
  106. }
  107. }
  108. - (void)viewDidLayoutSubviews {
  109. [super viewDidLayoutSubviews];
  110. //更新contentview位置
  111. self.contentView.frame = self.view.bounds;
  112. self.titleView.frame = CGRectMake(0, 0, self.contentView.bounds.size.width, self.config.titleViewHeight);
  113. //更新pageVC位置
  114. self.pageVC.view.frame = CGRectMake(0, self.config.titleViewHeight, self.contentView.bounds.size.width, self.contentView.bounds.size.height - self.config.titleViewHeight);
  115. //更新标题位置
  116. if (self.config.navHeight>0) {
  117. self.titleView.frame = CGRectMake(0,0, self.contentView.bounds.size.width, self.config.titleViewHeight);
  118. //更新pageVC位置
  119. //更新pageVC位置
  120. self.pageVC.view.frame = CGRectMake(0, self.config.titleViewHeight, self.contentView.bounds.size.width, self.contentView.bounds.size.height - self.config.titleViewHeight);
  121. self.view.frame=CGRectMake(0, self.config.navHeight, self.view.size.width, self.view.size.height-self.config.navHeight);
  122. }
  123. if (self.config.showTitleInNavigationBar) {
  124. self.pageVC.view.frame = self.contentView.bounds;
  125. }
  126. //自动选中当前位置_selectedIndex
  127. if (!self.pageVCDidLoad) {
  128. //设置加载标记为已加载
  129. self.pageVCDidLoad = true;
  130. [self switchToViewControllerAdIndex:_selectedIndex animated:false];
  131. }
  132. }
  133. #pragma mark -
  134. #pragma mark UIPageViewControllerDelegate
  135. //滚动切换时调用
  136. - (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
  137. self.pageVCAnimating = true;
  138. }
  139. //滚动切换时调用
  140. - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
  141. //如果向左滚动,当前位置-1
  142. if (self.scrollDirection == XLScrollDirectionLeft) {
  143. _selectedIndex = _selectedIndex <= 0 ? 0 : _selectedIndex - 1;
  144. }
  145. //如果向右滚动,当前位置+1
  146. if (self.scrollDirection == XLScrollDirectionRight) {
  147. _selectedIndex = _selectedIndex >= [self numberOfPage] - 1 ? [self numberOfPage] - 1 : _selectedIndex + 1;
  148. }
  149. //标题居中
  150. self.titleView.selectedIndex = _selectedIndex;
  151. //回调代理方法
  152. [self delegateSelectedAdIndex:_selectedIndex];
  153. //切换中属性更新
  154. self.pageVCAnimating = false;
  155. }
  156. #pragma mark -
  157. #pragma mark UIPageViewControllerDataSource
  158. - (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
  159. return [self viewControllerForIndex:_selectedIndex - 1];
  160. }
  161. - (nullable UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
  162. return [self viewControllerForIndex:_selectedIndex + 1];
  163. }
  164. #pragma mark -
  165. #pragma mark ScrollViewDelegate
  166. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  167. CGFloat value = scrollView.contentOffset.x - scrollView.bounds.size.width;
  168. //判断滚动方向
  169. if (value == 0) {
  170. self.scrollDirection = XLScrollDirectionNone;
  171. }else if (value < 0) {
  172. self.scrollDirection = XLScrollDirectionLeft;
  173. }else if (value > 0) {
  174. self.scrollDirection = XLScrollDirectionRight;
  175. }
  176. self.titleView.animationProgress = value/scrollView.bounds.size.width;
  177. }
  178. - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  179. self.titleView.stopAnimation = false;
  180. }
  181. #pragma mark -
  182. #pragma mark PageTitleViewDataSource&Delegate
  183. - (NSInteger)pageTitleViewNumberOfTitle {
  184. return [self numberOfPage];
  185. }
  186. - (NSString *)pageTitleViewTitleForIndex:(NSInteger)index {
  187. return [self titleForIndex:index];
  188. }
  189. - (XLPageTitleCell *)pageTitleViewCellForItemAtIndex:(NSInteger)index {
  190. if ([self.dataSource respondsToSelector:@selector(pageViewController:titleViewCellForItemAtIndex:)]) {
  191. return [self.dataSource pageViewController:self titleViewCellForItemAtIndex:index];
  192. }
  193. return nil;
  194. }
  195. - (BOOL)pageTitleViewDidSelectedAtIndex:(NSInteger)index {
  196. BOOL switchSuccess = [self switchToViewControllerAdIndex:index animated:true];
  197. if (!switchSuccess) {
  198. return false;
  199. }
  200. self.titleView.stopAnimation = true;
  201. [self delegateSelectedAdIndex:index];
  202. return true;
  203. }
  204. #pragma mark -
  205. #pragma mark Setter
  206. //设置选中位置
  207. - (void)setSelectedIndex:(NSInteger)selectedIndex {
  208. BOOL switchSuccess = [self switchToViewControllerAdIndex:selectedIndex animated:true];
  209. if (!switchSuccess) {return;}
  210. self.titleView.stopAnimation = true;
  211. }
  212. //滑动开关
  213. - (void)setScrollEnabled:(BOOL)scrollEnabled {
  214. _scrollEnabled = scrollEnabled;
  215. for (UIScrollView *scrollView in self.pageVC.view.subviews) {
  216. if ([scrollView isKindOfClass:[UIScrollView class]]) {
  217. scrollView.scrollEnabled = scrollEnabled;
  218. }
  219. }
  220. }
  221. //设置右侧按钮
  222. - (void)setRightButton:(UIButton *)rightButton {
  223. _titleView.rightButton = rightButton;
  224. }
  225. #pragma mark -
  226. #pragma mark 切换位置方法
  227. - (BOOL)switchToViewControllerAdIndex:(NSInteger)index animated:(BOOL)animated {
  228. if ([self numberOfPage] == 0) {return NO;}
  229. //如果正在加载中 返回
  230. if (self.pageVCAnimating) {return NO;}
  231. //设置正在加载标记
  232. BOOL animating = animated && index != _selectedIndex;
  233. self.pageVCAnimating = animating;
  234. //更新当前位置
  235. _selectedIndex = index;
  236. //设置滚动方向
  237. UIPageViewControllerNavigationDirection direction = UIPageViewControllerNavigationDirectionForward;
  238. if (_titleView.lastSelectedIndex > _selectedIndex) {
  239. direction = UIPageViewControllerNavigationDirectionReverse;
  240. }
  241. //设置当前展示VC
  242. __weak typeof(self)weakSelf = self;
  243. [self.pageVC setViewControllers:@[[self viewControllerForIndex:index]] direction:direction animated:animated completion:^(BOOL finished) {
  244. weakSelf.pageVCAnimating = false;
  245. }];
  246. //标题居中
  247. self.titleView.selectedIndex = _selectedIndex;
  248. return YES;
  249. }
  250. #pragma mark -
  251. #pragma mark 刷新方法
  252. - (void)reloadData {
  253. [self.titleView reloadData];
  254. }
  255. #pragma mark -
  256. #pragma mark 自定义方法
  257. - (void)registerClass:(Class)cellClass forTitleViewCellWithReuseIdentifier:(NSString *)identifier {
  258. [self.titleView registerClass:cellClass forTitleViewCellWithReuseIdentifier:identifier];
  259. }
  260. - (XLPageTitleCell *)dequeueReusableTitleViewCellWithIdentifier:(NSString *)identifier forIndex:(NSInteger)index {
  261. return [self.titleView dequeueReusableCellWithIdentifier:identifier forIndex:index];
  262. }
  263. #pragma mark -
  264. #pragma mark 辅助方法
  265. //指定位置的视图控制器
  266. - (UIViewController *)viewControllerForIndex:(NSInteger)index {
  267. //如果越界,则返回nil
  268. if (index < 0 || index >= [self numberOfPage]) {
  269. return nil;
  270. }
  271. //获取当前vc和当前标题
  272. UIViewController *currentVC = self.pageVC.viewControllers.firstObject;
  273. NSString *currentTitle = currentVC.title;
  274. NSString *targetTitle = [self titleForIndex:index];
  275. //如果和当前位置一样,则返回当前vc
  276. if ([currentTitle isEqualToString:targetTitle]) {
  277. return currentVC;
  278. }
  279. //如果之前显示过,则从内存中读取
  280. for (UIViewController *vc in self.shownVCArr) {
  281. if ([vc.title isEqualToString:targetTitle]) {
  282. return vc;
  283. }
  284. }
  285. //如果之前没显示过,则通过dataSource创建
  286. UIViewController *vc = [self.dataSource pageViewController:self viewControllerForIndex:index];
  287. vc.title = [self titleForIndex:index];
  288. [self.shownVCArr addObject:vc];
  289. [self addChildViewController:vc];
  290. return vc;
  291. }
  292. //指定位置的标题
  293. - (NSString *)titleForIndex:(NSInteger)index {
  294. return [self.dataSource pageViewController:self titleForIndex:index];
  295. }
  296. //总页数
  297. - (NSInteger)numberOfPage {
  298. return [self.dataSource pageViewControllerNumberOfPage];
  299. }
  300. //执行代理方法
  301. - (void)delegateSelectedAdIndex:(NSInteger)index {
  302. if (index == self.lastDelegateIndex) {return;}
  303. self.lastDelegateIndex = index;
  304. if ([self.delegate respondsToSelector:@selector(pageViewController:didSelectedAtIndex:)]) {
  305. [self.delegate pageViewController:self didSelectedAtIndex:index];
  306. }
  307. }
  308. @end