TYPagerController.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #import "TYPagerController.h"
  2. #define _window_width [UIScreen mainScreen].bounds.size.width
  3. typedef NS_ENUM(NSUInteger, TYPagerControllerDirection) {
  4. TYPagerControllerLeft,
  5. TYPagerControllerRight,
  6. TYPagerControllerNone,
  7. };
  8. @interface TYPagerController ()<UIScrollViewDelegate> {
  9. BOOL _needLayoutContentView;
  10. BOOL _scrollAnimated;
  11. BOOL _isTapScrollMoved;
  12. CGFloat _preOffsetX;
  13. struct {
  14. unsigned int transitionFromIndexToIndex :1;
  15. unsigned int transitionFromIndexToIndexProgress :1;
  16. }_delegateFlags;
  17. struct {
  18. unsigned int transitionFromIndexToIndex :1;
  19. unsigned int transitionFromIndexToIndexProgress :1;
  20. }_methodFlags;
  21. }
  22. @property (nonatomic, weak) UIScrollView *contentView;
  23. @property (nonatomic, strong) NSMutableDictionary *visibleControllers;
  24. @property (nonatomic, strong) NSCache *memoryCache;
  25. @property (nonatomic, assign) NSInteger countOfControllers;
  26. //@property (nonatomic, assign) NSInteger curIndex;
  27. @property (nonatomic, assign) NSInteger curProgressIndex;
  28. @property (nonatomic, assign) NSRange visibleRange;
  29. @end
  30. NS_INLINE CGRect frameForControllerAtIndex(NSInteger index, CGRect frame)
  31. {
  32. return CGRectMake(index * CGRectGetWidth(frame), 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
  33. }
  34. NS_INLINE NSRange visibleRangWithOffset(CGFloat offset,CGFloat width, NSInteger maxIndex)
  35. {
  36. NSInteger startIndex = offset/width;
  37. NSInteger endIndex = ceil((offset + width)/width);
  38. if (startIndex < 0) {
  39. startIndex = 0;
  40. }
  41. if (endIndex > maxIndex) {
  42. endIndex = maxIndex;
  43. }
  44. return NSMakeRange(startIndex, endIndex - startIndex);
  45. }
  46. @implementation TYPagerController
  47. - (instancetype)initWithCoder:(NSCoder *)aDecoder
  48. {
  49. if (self = [super initWithCoder:aDecoder]) {
  50. [self configureInitPropertys];
  51. }
  52. return self;
  53. }
  54. - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  55. {
  56. if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
  57. [self configureInitPropertys];
  58. }
  59. return self;
  60. }
  61. - (void)configureInitPropertys
  62. {
  63. _memoryCache = [[NSCache alloc]init];
  64. _changeIndexWhenScrollProgress = 0.5;
  65. _contentTopEdging = 8;
  66. }
  67. #pragma mark - life cycle
  68. - (void)viewDidLoad {
  69. [super viewDidLoad];
  70. // Do any additional setup after loading the view.
  71. if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
  72. self.edgesForExtendedLayout = UIRectEdgeNone;
  73. }
  74. self.view.backgroundColor = [UIColor whiteColor];
  75. // add horizenl scrollView
  76. [self addContentView];
  77. // set up propertys
  78. [self configurePropertys];
  79. }
  80. - (void)viewWillAppear:(BOOL)animated
  81. {
  82. [super viewWillAppear:animated];
  83. // [self layoutContentViewIfNeed];
  84. }
  85. - (void)viewWillLayoutSubviews
  86. {
  87. [super viewWillLayoutSubviews];
  88. // [self layoutContentViewIfNeed];
  89. }
  90. - (void)addContentView
  91. {
  92. UIScrollView *contentView = [[UIScrollView alloc]init];
  93. contentView.showsHorizontalScrollIndicator = NO;
  94. contentView.showsVerticalScrollIndicator = NO;
  95. contentView.pagingEnabled = YES;
  96. contentView.delegate = self;
  97. contentView.backgroundColor = kWhiteColor;
  98. contentView.bounces = NO;
  99. [self.view addSubview:contentView];
  100. _contentView = contentView;
  101. }
  102. #pragma mark - configre propertys
  103. - (void)configurePropertys
  104. {
  105. _visibleControllers = [NSMutableDictionary dictionary];
  106. _curIndex = 4;
  107. _curProgressIndex = 2;
  108. _preOffsetX = 0;
  109. _scrollAnimated = YES;
  110. [self configureMethods];
  111. }
  112. - (void)resetPropertys
  113. {
  114. [_memoryCache removeAllObjects];
  115. [_visibleControllers removeAllObjects];
  116. for (UIViewController *viewController in self.childViewControllers) {
  117. [self removeViewController:viewController];
  118. }
  119. // _curIndex = 1;
  120. // _curProgressIndex = 1;
  121. _preOffsetX = 0;
  122. }
  123. - (void)setDelegate:(id<TYPagerControllerDelegate>)delegate
  124. {
  125. _delegate = delegate;
  126. _delegateFlags.transitionFromIndexToIndex = [_delegate respondsToSelector:@selector(pagerController:transitionFromIndex:toIndex:animated:)];
  127. _delegateFlags.transitionFromIndexToIndexProgress = [_delegate respondsToSelector:@selector(pagerController:transitionFromIndex:toIndex:progress:)];
  128. }
  129. - (void)configureMethods
  130. {
  131. _methodFlags.transitionFromIndexToIndex = [self respondsToSelector:@selector(transitionFromIndex:toIndex:animated:)];
  132. _methodFlags.transitionFromIndexToIndexProgress = [self respondsToSelector:@selector(transitionFromIndex:toIndex:progress:)];
  133. }
  134. #pragma mark - public method
  135. - (void)reloadData
  136. {
  137. [self resetPropertys];
  138. [self updateContentView];
  139. }
  140. -(void)setContentFrame{
  141. [_contentView setContentOffset:CGPointMake(_window_width,0) animated:NO];
  142. }
  143. - (void)moveToControllerAtIndex:(NSInteger)index animated:(BOOL)animated
  144. {
  145. if (index < 0 || index >= _countOfControllers) {
  146. return;
  147. }
  148. _isTapScrollMoved = YES;
  149. _scrollAnimated = animated;
  150. [_contentView setContentOffset:CGPointMake(index * CGRectGetWidth(_contentView.frame),0) animated:NO];
  151. }
  152. - (NSArray *)visibleViewControllers
  153. {
  154. return [_visibleControllers allValues];
  155. }
  156. #pragma mark - layout content
  157. - (NSInteger)statusBarHeight
  158. {
  159. // return (_adjustStatusBarHeight && (!self.navigationController || self.navigationController.isNavigationBarHidden) && [[[UIDevice currentDevice] systemVersion] floatValue] >= 7) ? 20:0;
  160. return 20;
  161. }
  162. // if need layout contentView
  163. - (void)layoutContentViewIfNeed
  164. {
  165. // if (!CGSizeEqualToSize(_contentView.frame.size, CGSizeMake(CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) - _contentTopEdging - [self statusBarHeight]))) {
  166. // // size changed
  167. // [self updateContentView];
  168. // }
  169. }
  170. // update content subViews
  171. - (void)updateContentView
  172. {
  173. _needLayoutContentView = YES;
  174. _countOfControllers = [_dataSource numberOfControllersInPagerController];
  175. CGFloat contentTopEdging = _contentTopEdging;
  176. [self reSizeContentViewWithFrame:CGRectMake(0, contentTopEdging, CGRectGetWidth(self.view.frame),kRealValue(1000))];
  177. // kScreenH - contentTopEdging)];
  178. [self layoutContentView];
  179. }
  180. // change content View size
  181. -(void)reSizeContentViewWithFrame:(CGRect)frame{
  182. _contentView.frame = frame;
  183. _contentView.contentSize = CGSizeMake(_countOfControllers * CGRectGetWidth(_contentView.frame), frame.size.height);
  184. _contentView.contentOffset = CGPointMake(CGRectGetWidth(_contentView.frame), 0);
  185. }
  186. // layout content subViews
  187. - (void)layoutContentView
  188. {
  189. CGFloat offsetX = _contentView.contentOffset.x;
  190. // 获取可见range
  191. NSRange visibleRange = visibleRangWithOffset(offsetX, CGRectGetWidth(_contentView.frame), _countOfControllers);
  192. if (NSEqualRanges(_visibleRange, visibleRange) && !_needLayoutContentView) {
  193. return;
  194. }
  195. _needLayoutContentView = NO;
  196. _visibleRange = visibleRange;
  197. [self removeControllersOutOfVisibleRange:_visibleRange];
  198. [self addControllersInVisibleRange:_visibleRange];
  199. }
  200. // caculate pager index
  201. - (void)configurePagerIndex
  202. {
  203. CGFloat offsetX = _contentView.contentOffset.x;
  204. CGFloat width = CGRectGetWidth(_contentView.frame);
  205. // scroll direction
  206. TYPagerControllerDirection direction = offsetX >= _preOffsetX ? TYPagerControllerLeft : TYPagerControllerRight;
  207. NSInteger index = 0;
  208. // when scroll progress percent will change index
  209. CGFloat percentChangeIndex = 1.0-_changeIndexWhenScrollProgress;
  210. // caculate cur index
  211. if (direction == TYPagerControllerLeft) {
  212. index = offsetX/width+percentChangeIndex;
  213. }else {
  214. index = ceil(offsetX/width-percentChangeIndex);
  215. }
  216. if (index < 0) {
  217. index = 0;
  218. }else if (index >= _countOfControllers) {
  219. index = _countOfControllers-1;
  220. }
  221. // if index not same,change index
  222. if (index != _curIndex) {
  223. NSInteger fromIndex = _curIndex;
  224. _curIndex = index;
  225. if (_methodFlags.transitionFromIndexToIndex) {
  226. [self transitionFromIndex:fromIndex toIndex:_curIndex animated:_scrollAnimated];
  227. }
  228. if (_delegateFlags.transitionFromIndexToIndex) {
  229. [_delegate pagerController:self transitionFromIndex:fromIndex toIndex:_curIndex animated:_scrollAnimated];
  230. }
  231. }
  232. _scrollAnimated = YES;
  233. }
  234. // caculate pager index and progress
  235. - (void)configurePagerIndexByProgress
  236. {
  237. CGFloat offsetX = _contentView.contentOffset.x;
  238. CGFloat width = CGRectGetWidth(_contentView.frame);
  239. CGFloat floorIndex = floor(offsetX/width);
  240. CGFloat progress = offsetX/width-floorIndex;
  241. if (floorIndex < 0 || floorIndex >= _countOfControllers) {
  242. return;
  243. }
  244. TYPagerControllerDirection direction = offsetX >= _preOffsetX ? TYPagerControllerLeft : TYPagerControllerRight;
  245. NSInteger fromIndex = 0, toIndex = 0;
  246. if (direction == TYPagerControllerLeft) {
  247. if (floorIndex >= _countOfControllers -1) {
  248. return;
  249. }
  250. fromIndex = floorIndex;
  251. toIndex = MIN(_countOfControllers-1, fromIndex + 1);
  252. }else {
  253. if (floorIndex < 0 ) {
  254. return;
  255. }
  256. toIndex = floorIndex;
  257. fromIndex = MIN(_countOfControllers-1, toIndex +1);
  258. progress = 1.0 - progress;
  259. }
  260. if (_methodFlags.transitionFromIndexToIndexProgress) {
  261. [self transitionFromIndex:fromIndex toIndex:toIndex progress:progress];
  262. }
  263. if (_delegateFlags.transitionFromIndexToIndexProgress) {
  264. [_delegate pagerController:self transitionFromIndex:fromIndex toIndex:toIndex progress:progress];
  265. }
  266. }
  267. // is scrolling and caculate progess ?
  268. - (BOOL)isProgressScrollEnabel
  269. {
  270. return (_delegateFlags.transitionFromIndexToIndexProgress || _methodFlags.transitionFromIndexToIndexProgress) && !_isTapScrollMoved ;
  271. }
  272. #pragma mark - remove controller
  273. // remove pager controller if it out of visible range
  274. - (void)removeControllersOutOfVisibleRange:(NSRange)range
  275. {
  276. NSMutableArray *deleteArray = [NSMutableArray array];
  277. [_visibleControllers enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, UIViewController *viewController, BOOL * stop) {
  278. NSInteger indexOfController = [key integerValue];
  279. if (!NSLocationInRange(indexOfController, range)) {
  280. // unvisible
  281. [self removeViewController:viewController atIndex:indexOfController];
  282. [deleteArray addObject:key];
  283. }else {
  284. [self addViewController:viewController atIndex:indexOfController];
  285. }
  286. }];
  287. [_visibleControllers removeObjectsForKeys:deleteArray];
  288. }
  289. // remove pager controller at index
  290. - (void)removeViewController:(UIViewController *)viewController atIndex:(NSInteger)index
  291. {
  292. if (viewController.parentViewController) {
  293. [self removeViewController:viewController];
  294. // remove and cache
  295. if (![_memoryCache objectForKey:@(index)]) {
  296. [_memoryCache setObject:viewController forKey:@(index)];
  297. }
  298. }
  299. }
  300. - (void)removeViewController:(UIViewController *)viewController
  301. {
  302. [viewController willMoveToParentViewController:nil];
  303. [viewController.view removeFromSuperview];
  304. [viewController removeFromParentViewController];
  305. }
  306. #pragma mark - add controller
  307. // add pager controller if it in visible range
  308. - (void)addControllersInVisibleRange:(NSRange)range
  309. {
  310. NSInteger endIndex = range.location + range.length;
  311. for (NSInteger idx = range.location ; idx < endIndex; ++idx) {
  312. UIViewController *viewController = [_visibleControllers objectForKey:@(idx)];
  313. if (!viewController) {
  314. viewController = [_memoryCache objectForKey:@(idx)];
  315. }
  316. if (!viewController) {
  317. viewController = [_dataSource pagerController:self controllerForIndex:idx];
  318. }
  319. [self addViewController:viewController atIndex:idx];
  320. }
  321. }
  322. // add pager controller at index
  323. - (void)addViewController:(UIViewController *)viewController atIndex:(NSInteger)index
  324. {
  325. if (!viewController.parentViewController) {
  326. // addChildViewController
  327. [self addChildViewController:viewController];
  328. [_contentView addSubview:viewController.view];
  329. [viewController didMoveToParentViewController:self];
  330. if (![_visibleControllers objectForKey:@(index)]) {
  331. [_visibleControllers setObject:viewController forKey:@(index)];
  332. }
  333. }else {
  334. // if VC have parentViewController,change the frame
  335. // viewController.view.frame = frameForControllerAtIndex(index, _contentVCFrame);
  336. }
  337. viewController.view.frame = CGRectMake(index * kScreenW, 0, kScreenW, kScreenH);
  338. }
  339. #pragma mark - UIScrollViewDelegate
  340. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  341. {
  342. if (scrollView == _contentView && _countOfControllers > 0) {
  343. if ([self isProgressScrollEnabel] && !_needLayoutContentView) {
  344. // caculate scroll progress
  345. [self configurePagerIndexByProgress];
  346. }
  347. if (!_needLayoutContentView) {
  348. // caculate scroll index
  349. [self configurePagerIndex];
  350. }
  351. // layout
  352. [self layoutContentView];
  353. _isTapScrollMoved = NO;
  354. }
  355. }
  356. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
  357. {
  358. if (scrollView == _contentView) {
  359. // save offsetX ,judge scroll direction
  360. _preOffsetX = scrollView.contentOffset.x;
  361. }
  362. }
  363. - (void)didReceiveMemoryWarning {
  364. [super didReceiveMemoryWarning];
  365. // Dispose of any resources that can be recreated.
  366. [_memoryCache removeAllObjects];
  367. }
  368. - (void)dealloc
  369. {
  370. [_memoryCache removeAllObjects];
  371. [_visibleControllers removeAllObjects];
  372. }
  373. @end