New file |
| | |
| | | // |
| | | // ZJContentView.m |
| | | // ZJScrollPageView |
| | | // |
| | | // Created by jasnig on 16/5/6. |
| | | // Copyright © 2016年 ZeroJ. All rights reserved. |
| | | // |
| | | |
| | | #import "ZJContentView.h" |
| | | #import "ZJScrollSegmentView.h" |
| | | #import "UIViewController+ZJScrollPageController.h" |
| | | |
| | | typedef NS_ENUM(NSInteger, ZJScrollPageControllerScrollDirection) { |
| | | ZJScrollPageControllerScrollDirectionNone, |
| | | ZJScrollPageControllerScrollDirectionLeft, |
| | | ZJScrollPageControllerScrollDirectionRight |
| | | }; |
| | | |
| | | @interface ZJContentView ()<UIScrollViewDelegate, UIGestureRecognizerDelegate> { |
| | | CGFloat _oldOffSetX; |
| | | BOOL _isLoadFirstView; |
| | | // BOOL _isAnimating; |
| | | } |
| | | /** 避免循环引用*/ |
| | | @property (weak, nonatomic) ZJScrollSegmentView *segmentView; |
| | | |
| | | |
| | | // 父类 用于处理添加子控制器 使用weak避免循环引用 |
| | | @property (weak, nonatomic) UIViewController *parentViewController; |
| | | // 当这个属性设置为YES的时候 就不用处理 scrollView滚动的计算 |
| | | @property (assign, nonatomic) BOOL forbidTouchToAdjustPosition; |
| | | @property (assign, nonatomic) NSInteger itemsCount; |
| | | // 所有的子控制器 |
| | | @property (strong, nonatomic) NSMutableDictionary<NSString *, UIViewController<ZJScrollPageViewChildVcDelegate> *> *childVcsDic; |
| | | // 当前控制器 |
| | | @property (strong, nonatomic) UIViewController<ZJScrollPageViewChildVcDelegate> *currentChildVc; |
| | | |
| | | /// 如果类似cell缓存一样, 虽然创建的控制器少了, 但是每个页面每次都要重新加载数据, 否则显示的内容就会出错, 貌似还不如每个页面创建一个控制器好 |
| | | //@property (strong, nonatomic) NSCache *cacheChildVcs; |
| | | |
| | | @property (strong, nonatomic) UIScrollView *scrollView; |
| | | @property (strong, nonatomic) UIView *currentView; |
| | | @property (strong, nonatomic) UIView *oldView; |
| | | @property (assign, nonatomic) NSInteger currentIndex; |
| | | @property (assign, nonatomic) NSInteger oldIndex; |
| | | @property (assign, nonatomic) ZJScrollPageControllerScrollDirection scrollDirection; |
| | | |
| | | @end |
| | | |
| | | @implementation ZJContentView |
| | | #define cellID @"cellID" |
| | | |
| | | static NSString *const kContentOffsetOffKey = @"contentOffset"; |
| | | #pragma mark - life cycle |
| | | |
| | | - (instancetype)initWithFrame:(CGRect)frame segmentView:(ZJScrollSegmentView *)segmentView parentViewController:(UIViewController *)parentViewController delegate:(id<ZJScrollPageViewDelegate>) delegate { |
| | | |
| | | if (self = [super initWithFrame:frame]) { |
| | | self.segmentView = segmentView; |
| | | self.delegate = delegate; |
| | | self.parentViewController = parentViewController; |
| | | |
| | | [self commonInit]; |
| | | [self addNotification]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (void)commonInit { |
| | | |
| | | _oldIndex = -1; |
| | | _currentIndex = -1; |
| | | _oldOffSetX = 0.0f; |
| | | _forbidTouchToAdjustPosition = NO; |
| | | _isLoadFirstView = YES; |
| | | if ([_delegate respondsToSelector:@selector(numberOfChildViewControllers)]) { |
| | | self.itemsCount = [_delegate numberOfChildViewControllers]; |
| | | } |
| | | [self addSubview:self.scrollView]; |
| | | |
| | | [self setCurrentIndex:0 andScrollDirection:ZJScrollPageControllerScrollDirectionNone]; |
| | | |
| | | if (self.parentViewController.parentViewController && [self.parentViewController.parentViewController isKindOfClass:[UINavigationController class]]) { |
| | | UINavigationController *navi = (UINavigationController *)self.parentViewController.parentViewController; |
| | | |
| | | if (navi.interactivePopGestureRecognizer) { |
| | | navi.interactivePopGestureRecognizer.delegate = self; |
| | | [self.scrollView.panGestureRecognizer requireGestureRecognizerToFail:navi.interactivePopGestureRecognizer]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)addNotification { |
| | | |
| | | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveMemoryWarningHander:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; |
| | | } |
| | | |
| | | - (void)receiveMemoryWarningHander:(NSNotificationCenter *)noti { |
| | | |
| | | __weak typeof(self) weakSelf = self; |
| | | [_childVcsDic enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, UIViewController<ZJScrollPageViewChildVcDelegate> * _Nonnull childVc, BOOL * _Nonnull stop) { |
| | | __strong typeof(self) strongSelf = weakSelf; |
| | | |
| | | if (childVc != strongSelf.currentChildVc) { |
| | | [_childVcsDic removeObjectForKey:key]; |
| | | [ZJContentView removeChildVc:childVc]; |
| | | } |
| | | |
| | | }]; |
| | | |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | self.parentViewController = nil; |
| | | [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| | | #if DEBUG |
| | | NSLog(@"ZJContentView---销毁"); |
| | | #endif |
| | | } |
| | | |
| | | #pragma mark - public helper |
| | | |
| | | /** 给外界可以设置ContentOffSet的方法 */ |
| | | - (void)setContentOffSet:(CGPoint)offset animated:(BOOL)animated { |
| | | self.forbidTouchToAdjustPosition = YES; |
| | | |
| | | NSInteger currentIndex = offset.x/self.scrollView.bounds.size.width; |
| | | _oldIndex = _currentIndex; |
| | | [self setCurrentIndex:currentIndex andScrollDirection:ZJScrollPageControllerScrollDirectionNone]; |
| | | |
| | | if (animated) { |
| | | CGFloat delta = offset.x - self.scrollView.contentOffset.x; |
| | | NSInteger page = fabs(delta)/self.scrollView.bounds.size.width; |
| | | if (page>=2) {// 需要滚动两页以上的时候, 跳过中间页的动画 |
| | | |
| | | __weak typeof(self) weakself = self; |
| | | dispatch_async(dispatch_get_main_queue(), ^{ |
| | | __strong typeof(weakself) strongSelf = weakself; |
| | | if (strongSelf) { |
| | | [strongSelf.scrollView setContentOffset:offset animated:NO]; |
| | | [self didAppearWithIndex:currentIndex]; |
| | | [self didDisappearWithIndex:_oldIndex]; |
| | | } |
| | | }); |
| | | } |
| | | else { |
| | | [self.scrollView setContentOffset:offset animated:animated]; |
| | | [self didAppearWithIndex:currentIndex]; |
| | | [self didDisappearWithIndex:_oldIndex]; |
| | | |
| | | } |
| | | } |
| | | else { |
| | | [self.scrollView setContentOffset:offset animated:animated]; |
| | | [self didAppearWithIndex:currentIndex]; |
| | | [self didDisappearWithIndex:_oldIndex]; |
| | | |
| | | |
| | | } |
| | | |
| | | } |
| | | |
| | | /** 给外界刷新视图的方法 */ |
| | | - (void)reload { |
| | | |
| | | [self.childVcsDic enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, UIViewController<ZJScrollPageViewChildVcDelegate> * _Nonnull childVc, BOOL * _Nonnull stop) { |
| | | [ZJContentView removeChildVc:childVc]; |
| | | childVc = nil; |
| | | |
| | | }]; |
| | | self.childVcsDic = nil; |
| | | [self.scrollView removeFromSuperview]; |
| | | self.scrollView = nil; |
| | | [self commonInit]; |
| | | } |
| | | |
| | | + (void)removeChildVc:(UIViewController *)childVc { |
| | | [childVc willMoveToParentViewController:nil]; |
| | | [childVc.view removeFromSuperview]; |
| | | [childVc removeFromParentViewController]; |
| | | } |
| | | |
| | | #pragma mark - UIScrollViewDelegate |
| | | |
| | | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { |
| | | if (self.forbidTouchToAdjustPosition) {// first or last |
| | | return; |
| | | } |
| | | |
| | | if (scrollView.contentOffset.x <= 0) { |
| | | [self contentViewDidMoveFromIndex:0 toIndex:0 progress:1.0]; |
| | | return; |
| | | |
| | | } |
| | | if (scrollView.contentOffset.x >= scrollView.contentSize.width - scrollView.bounds.size.width) { |
| | | [self contentViewDidMoveFromIndex:_itemsCount-1 toIndex:_itemsCount-1 progress:1.0]; |
| | | return; |
| | | |
| | | } |
| | | |
| | | CGFloat tempProgress = scrollView.contentOffset.x / self.bounds.size.width; |
| | | NSInteger tempIndex = (NSInteger)tempProgress; |
| | | CGFloat progress = tempProgress - floor(tempProgress); |
| | | CGFloat deltaX = scrollView.contentOffset.x - _oldOffSetX; |
| | | |
| | | if (deltaX > 0 && (deltaX != scrollView.bounds.size.width)) {// 向右 |
| | | _oldIndex = tempIndex; |
| | | NSInteger tempCurrentIndex = tempIndex + 1; |
| | | |
| | | [self setCurrentIndex:tempCurrentIndex andScrollDirection:ZJScrollPageControllerScrollDirectionRight]; |
| | | |
| | | } |
| | | else if (deltaX < 0) { |
| | | progress = 1.0 - progress; |
| | | |
| | | _oldIndex = tempIndex + 1; |
| | | |
| | | [self setCurrentIndex:tempIndex andScrollDirection:ZJScrollPageControllerScrollDirectionLeft]; |
| | | |
| | | } |
| | | else { |
| | | return; |
| | | } |
| | | |
| | | NSLog(@"%f------%ld----%ld------", progress, _oldIndex, _currentIndex); |
| | | |
| | | [self contentViewDidMoveFromIndex:_oldIndex toIndex:_currentIndex progress:progress]; |
| | | |
| | | } |
| | | |
| | | /** 滚动减速完成时再更新title的位置 */ |
| | | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { |
| | | NSInteger currentIndex = (scrollView.contentOffset.x / self.bounds.size.width); |
| | | if (_scrollDirection == ZJScrollPageControllerScrollDirectionNone && !_forbidTouchToAdjustPosition) { // 开启bounds 在第一页和最后一页快速松开手又接触滑动的时候 会不合理的被调用这个代理方法 ---- 其实这个时候并没有在松开手的情况下减速完成 |
| | | return; |
| | | } |
| | | [self contentViewDidMoveFromIndex:currentIndex toIndex:currentIndex progress:1.0]; |
| | | // 调整title |
| | | [self adjustSegmentTitleOffsetToCurrentIndex:currentIndex]; |
| | | |
| | | if (scrollView.contentOffset.x == _oldOffSetX) {// 滚动未完成 |
| | | |
| | | [self didAppearWithIndex:currentIndex]; |
| | | [self didDisappearWithIndex:_currentIndex]; |
| | | } |
| | | else { |
| | | [self didAppearWithIndex:_currentIndex]; |
| | | [self didDisappearWithIndex:_oldIndex]; |
| | | } |
| | | // 重置_currentIndex 不触发set方法 |
| | | _currentIndex = currentIndex; |
| | | _scrollDirection = ZJScrollPageControllerScrollDirectionNone; |
| | | |
| | | } |
| | | |
| | | |
| | | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { |
| | | _oldOffSetX = scrollView.contentOffset.x; |
| | | self.forbidTouchToAdjustPosition = NO; |
| | | _scrollDirection = ZJScrollPageControllerScrollDirectionNone; |
| | | |
| | | } |
| | | |
| | | |
| | | #pragma mark - private helper |
| | | - (void)contentViewDidMoveFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex progress:(CGFloat)progress { |
| | | if(self.segmentView) { |
| | | [self.segmentView adjustUIWithProgress:progress oldIndex:fromIndex currentIndex:toIndex]; |
| | | } |
| | | } |
| | | |
| | | - (void)adjustSegmentTitleOffsetToCurrentIndex:(NSInteger)index { |
| | | if(self.segmentView) { |
| | | [self.segmentView adjustTitleOffSetToCurrentIndex:index]; |
| | | } |
| | | |
| | | } |
| | | |
| | | - (void)willAppearWithIndex:(NSInteger)index { |
| | | UIViewController<ZJScrollPageViewChildVcDelegate> *controller = [self.childVcsDic valueForKey:[NSString stringWithFormat:@"%ld", index]]; |
| | | if (controller) { |
| | | if ([controller respondsToSelector:@selector(zj_viewWillAppearForIndex:)]) { |
| | | [controller zj_viewWillAppearForIndex:index]; |
| | | } |
| | | |
| | | if (_delegate && [_delegate respondsToSelector:@selector(scrollPageController:childViewControllWillAppear:forIndex:)]) { |
| | | [_delegate scrollPageController:self.parentViewController childViewControllWillAppear:controller forIndex:index]; |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | - (void)didAppearWithIndex:(NSInteger)index { |
| | | UIViewController<ZJScrollPageViewChildVcDelegate> *controller = [self.childVcsDic valueForKey:[NSString stringWithFormat:@"%ld", index]]; |
| | | if (controller) { |
| | | if ([controller respondsToSelector:@selector(zj_viewDidAppearForIndex:)]) { |
| | | [controller zj_viewDidAppearForIndex:index]; |
| | | } |
| | | |
| | | if (_delegate && [_delegate respondsToSelector:@selector(scrollPageController:childViewControllDidAppear:forIndex:)]) { |
| | | [_delegate scrollPageController:self.parentViewController childViewControllDidAppear:controller forIndex:index]; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| | | |
| | | - (void)willDisappearWithIndex:(NSInteger)index { |
| | | UIViewController<ZJScrollPageViewChildVcDelegate> *controller = [self.childVcsDic valueForKey:[NSString stringWithFormat:@"%ld", index]]; |
| | | if (controller) { |
| | | if ([controller respondsToSelector:@selector(zj_viewWillDisappearForIndex:)]) { |
| | | [controller zj_viewWillDisappearForIndex:index]; |
| | | } |
| | | if (_delegate && [_delegate respondsToSelector:@selector(scrollPageController:childViewControllWillDisappear:forIndex:)]) { |
| | | [_delegate scrollPageController:self.parentViewController childViewControllWillDisappear:controller forIndex:index]; |
| | | } |
| | | } |
| | | |
| | | } |
| | | - (void)didDisappearWithIndex:(NSInteger)index { |
| | | UIViewController<ZJScrollPageViewChildVcDelegate> *controller = [self.childVcsDic valueForKey:[NSString stringWithFormat:@"%ld", index]]; |
| | | if (controller) { |
| | | if ([controller respondsToSelector:@selector(zj_viewDidDisappearForIndex:)]) { |
| | | [controller zj_viewDidDisappearForIndex:index]; |
| | | } |
| | | if (_delegate && [_delegate respondsToSelector:@selector(scrollPageController:childViewControllDidDisappear:forIndex:)]) { |
| | | [_delegate scrollPageController:self.parentViewController childViewControllDidDisappear:controller forIndex:index]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | - (void)setCurrentIndex:(NSInteger)currentIndex andScrollDirection:(ZJScrollPageControllerScrollDirection)scrollDirection { |
| | | if (currentIndex != _currentIndex) { |
| | | |
| | | // NSLog(@"current -- %ld _current ---- %ld _oldIndex --- %ld", currentIndex, _currentIndex, _oldIndex); |
| | | [self setupSubviewsWithCurrentIndex:currentIndex oldIndex:_oldIndex]; |
| | | |
| | | if (scrollDirection != ZJScrollPageControllerScrollDirectionNone) { |
| | | // 打开右边, 但是未松手又返回了打开左边 |
| | | // 打开左边, 但是未松手又返回了打开右边 |
| | | [self didDisappearWithIndex:_currentIndex]; |
| | | |
| | | } |
| | | [self willAppearWithIndex:currentIndex]; |
| | | [self willDisappearWithIndex:_oldIndex]; |
| | | if (_isLoadFirstView) { // 加载第一个controller的时候 同时出发didAppear |
| | | [self didAppearWithIndex:currentIndex]; |
| | | _isLoadFirstView = NO; |
| | | } |
| | | |
| | | _scrollDirection = scrollDirection; |
| | | _currentIndex = currentIndex; |
| | | |
| | | // NSLog(@"%@",self.scrollView.subviews); |
| | | } |
| | | |
| | | } |
| | | |
| | | - (void)setupSubviewsWithCurrentIndex:(NSInteger)currentIndex oldIndex:(NSInteger)oldIndex { |
| | | UIViewController<ZJScrollPageViewChildVcDelegate> *currentController = [self.childVcsDic valueForKey:[NSString stringWithFormat:@"%ld", (long)currentIndex]]; |
| | | |
| | | if (_delegate && [_delegate respondsToSelector:@selector(childViewController:forIndex:)]) { |
| | | if (currentController == nil) { |
| | | currentController = [_delegate childViewController:nil forIndex:currentIndex]; |
| | | [self.childVcsDic setValue:currentController forKey:[NSString stringWithFormat:@"%ld", (long)currentIndex]]; |
| | | } else { |
| | | [_delegate childViewController:currentController forIndex:currentIndex]; |
| | | } |
| | | } else { |
| | | NSAssert(NO, @"必须设置代理和实现代理方法"); |
| | | } |
| | | |
| | | if ([currentController isKindOfClass:[UINavigationController class]]) { |
| | | NSAssert(NO, @"不要添加UINavigationController包装后的子控制器"); |
| | | |
| | | } |
| | | |
| | | CGFloat width = self.bounds.size.width; |
| | | CGFloat height = self.bounds.size.height; |
| | | |
| | | // 添加currentController |
| | | if (currentController.zj_scrollViewController != self.parentViewController) { |
| | | [self.parentViewController addChildViewController:currentController]; |
| | | } |
| | | [self.currentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; |
| | | self.currentView.frame = CGRectMake(currentIndex*width, 0, width, height); |
| | | [self.currentView addSubview:currentController.view]; |
| | | [_currentChildVc didMoveToParentViewController:self.parentViewController]; |
| | | |
| | | UIViewController *oldController = [self.childVcsDic valueForKey:[NSString stringWithFormat:@"%ld", (long)_oldIndex]]; |
| | | // 添加oldController |
| | | if (oldController) { |
| | | |
| | | [self.oldView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; |
| | | self.oldView.frame = CGRectMake(oldIndex*width, 0, width, height); |
| | | [self.oldView addSubview:oldController.view]; |
| | | [oldController didMoveToParentViewController:self.parentViewController]; |
| | | } |
| | | |
| | | [self setNeedsLayout]; |
| | | } |
| | | |
| | | |
| | | |
| | | #pragma mark - UIGestureRecognizerDelegate |
| | | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { |
| | | if (self.parentViewController.parentViewController && [self.parentViewController.parentViewController isKindOfClass:[UINavigationController class]]) { |
| | | UINavigationController *navi = (UINavigationController *)self.parentViewController.parentViewController; |
| | | |
| | | if (navi.visibleViewController == self.parentViewController) {// 当显示的是ScrollPageView的时候 只在第一个tag处执行pop手势 |
| | | |
| | | return self.scrollView.contentOffset.x == 0; |
| | | } else { |
| | | return [super gestureRecognizerShouldBegin:gestureRecognizer]; |
| | | } |
| | | } |
| | | return [super gestureRecognizerShouldBegin:gestureRecognizer]; |
| | | } |
| | | |
| | | |
| | | #pragma mark - getter --- setter |
| | | - (UIScrollView *)scrollView { |
| | | if (!_scrollView) { |
| | | UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.bounds]; |
| | | |
| | | self.currentView = [[UIView alloc] init]; |
| | | self.oldView = [[UIView alloc] init]; |
| | | scrollView.delegate = self; |
| | | [scrollView addSubview:self.currentView]; |
| | | [scrollView addSubview:self.oldView]; |
| | | |
| | | scrollView.pagingEnabled = YES; |
| | | scrollView.showsVerticalScrollIndicator = NO; |
| | | scrollView.showsHorizontalScrollIndicator = NO; |
| | | scrollView.bounces = self.segmentView.segmentStyle.isContentViewBounces; |
| | | scrollView.scrollEnabled = self.segmentView.segmentStyle.isScrollContentView; |
| | | scrollView.contentSize = CGSizeMake(self.itemsCount*self.bounds.size.width, self.bounds.size.height); |
| | | |
| | | _scrollView = scrollView; |
| | | } |
| | | return _scrollView; |
| | | } |
| | | |
| | | - (NSMutableDictionary<NSString *,UIViewController<ZJScrollPageViewChildVcDelegate> *> *)childVcsDic { |
| | | if (!_childVcsDic) { |
| | | NSMutableDictionary *dic = [NSMutableDictionary dictionary]; |
| | | _childVcsDic = dic; |
| | | } |
| | | return _childVcsDic; |
| | | } |
| | | |
| | | @end |