// // MTPageView.m // MTPage // // Created by ko1o on 2019/5/6. // Copyright © 2019年 ko1o. All rights reserved. // #import "MTPageView.h" @interface MTPageParetViewController : UIViewController @end @implementation MTPageParetViewController - (BOOL)shouldAutomaticallyForwardAppearanceMethods { return NO; } @end @interface MTPageItem : NSObject @property (nonatomic, strong) id itemModel; @property (nonatomic, strong) UIView *menuItemView; @property (nonatomic, strong) UIView *menuItemContentView; @property (nonatomic, strong) UIViewController *viewController; @end @implementation MTPageItem - (void)dealloc { [self.menuItemView removeFromSuperview]; [self.menuItemContentView removeFromSuperview]; [self.viewController willMoveToParentViewController:nil]; [self.viewController removeFromParentViewController]; self.viewController = nil; } @end @interface MTPageView () @property (nonatomic, strong) MTPageConfig *config; @property (nonatomic, strong) NSMutableArray *pageItems; @property (nonatomic, copy) void(^itemDidSelectBlock)(id, NSInteger); @property (nonatomic, assign) NSInteger currentIndex; @property (nonatomic, strong) MTPageParetViewController *parentViewController; @property (nonatomic, strong) UIView *sliderBar; @property (nonatomic, strong) UIView *bottomLineView; @property (nonatomic, strong) UIActivityIndicatorView *loadingView; @property (nonatomic, copy) void (^setupConfigBlock)(MTPageConfig * _Nonnull); @end @implementation MTPageView - (UIViewController *)pageViewController { return _parentViewController; } - (instancetype)init { if (self = [super init]) { [self setupConfig]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { [self setupConfig]; } return self; } - (UIScrollView *)menuContentScrollView { if (!_menuContentScrollView) { _menuContentScrollView = [[UIScrollView alloc] initWithFrame:CGRectZero]; } return _menuContentScrollView; } - (MTPageConfig *)pageConfig { return _config; } - (void)startLoading { self.pageContentScrollView.hidden = YES; self.menuContentScrollView.hidden = YES; self.loadingView.alpha = 1.0; } - (BOOL)isLoading { return self.loadingView.alpha >= 0.99; } - (void)endLoadingWithItemModels:(NSArray *)itemModels { self.pageContentScrollView.hidden = NO; self.menuContentScrollView.hidden = NO; self.loadingView.alpha = 0.0; if (self.setupConfigBlock) { self.setupConfigBlock(self.config); } [self setupUIWithItemModels:itemModels]; [self relayoutUI]; } - (void)setupConfig { self.config = [[MTPageConfig alloc] init]; self.parentViewController = [[MTPageParetViewController alloc] init]; [self addSubview:self.parentViewController.view]; self.loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; self.loadingView.alpha = 0.0; self.loadingView.centerX = self.width * 0.5; self.loadingView.y = 20; [self.loadingView startAnimating]; self.menuContentScrollView.frame = CGRectMake(0, 0, self.config.menuContentWidth, 0); UIView *bottomView = [[UIView alloc] init]; bottomView.width = SCREEN_WIDTH; bottomView.height = 0.7; bottomView.backgroundColor = COLOR_WITH_RGB(0xeeeeee); [self.menuContentScrollView addSubview:bottomView]; self.bottomLineView = bottomView; self.menuContentScrollView.clipsToBounds = NO; self.sliderBar = [[UIView alloc] init]; self.sliderBar.width = 30.0; self.sliderBar.height = 2.0; self.sliderBar.layer.cornerRadius = 1; self.sliderBar.backgroundColor = COLOR_WITH_RGB(0x333333); [self.menuContentScrollView addSubview:self.sliderBar]; [self addSubview:self.loadingView]; } - (void)setFrame:(CGRect)frame { [super setFrame:frame]; self.parentViewController.view.frame = self.bounds; self.loadingView.centerX = self.width * 0.5; _pageContentScrollView.frame = CGRectMake(0, CGRectGetMaxY(_menuContentScrollView.frame), self.frame.size.width, self.frame.size.height - CGRectGetMaxY(_menuContentScrollView.frame)); _pageContentScrollView.bounces = NO; for (MTPageItem *item in [self.pageItems mutableCopy]) { CGRect pageViewFrame = item.viewController.view.frame; pageViewFrame.size = _pageContentScrollView.bounds.size; item.viewController.view.frame = pageViewFrame; } } - (void)layoutSubviews { [super layoutSubviews]; [self.menuContentScrollView addSubview:self.sliderBar]; self.sliderBar.bottom = self.menuContentScrollView.height - self.menuContentScrollView.contentInset.bottom - 10; self.sliderBar.hidden = self.pageItems.count == 0; if (self.config.didLayoutSubviewsBlock) { self.config.didLayoutSubviewsBlock(self, self.pageContentScrollView, self.menuContentScrollView, self.sliderBar); } if (self.pageItems.count > self.currentIndex) { MTPageItem *pageItem = self.pageItems[self.currentIndex]; self.sliderBar.centerX = pageItem.menuItemView.superview.centerX; self.sliderBar.bottom = self.menuContentScrollView.height - self.menuContentScrollView.contentInset.top - self.menuContentScrollView.contentInset.bottom + 1; [self.menuContentScrollView.superview insertSubview:self.bottomLineView belowSubview:self.menuContentScrollView]; self.bottomLineView.y = self.menuContentScrollView.height; self.bottomLineView.hidden = !self.config.showBottomLine; self.menuContentScrollView.clipsToBounds = YES; self.pageContentScrollView.height = self.height - self.pageContentScrollView.y; } } - (void)setupUIWithItemModels:(NSArray *)itemModels { MTPageConfig *config = self.config; self.currentIndex = -1; _pageItems = [NSMutableArray array]; _menuContentScrollView.contentInset = config.menuContentInset; _menuContentScrollView.showsHorizontalScrollIndicator = NO; [self addSubview:_menuContentScrollView]; _pageContentScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(_menuContentScrollView.frame), self.frame.size.width, self.frame.size.height - CGRectGetMaxY(_menuContentScrollView.frame))]; _pageContentScrollView.pagingEnabled = YES; _pageContentScrollView.showsHorizontalScrollIndicator = NO; _pageContentScrollView.scrollEnabled = self.config.pageScrollEnable; _pageContentScrollView.delegate = self; [self.parentViewController.view addSubview:_pageContentScrollView]; [_menuContentScrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; [_pageContentScrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; for (id itemModel in itemModels) { [self addItemWithModel:itemModel needRelayout:NO]; } UIView *lastMenuItemContentView = self.pageItems.lastObject.menuItemContentView; UIView *lastPageView = self.pageItems.lastObject.viewController.view; _menuContentScrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastMenuItemContentView.frame), 0); _pageContentScrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastPageView.frame), 0); [self selectItemAtIndex:config.defaultPageIndex animated:NO]; } - (void)viewWillAppear:(BOOL)animated { UIViewController *currentVc = self.pageItems[self.currentIndex].viewController; [currentVc viewWillAppear:animated]; } - (void)relayoutUI { UIView *lastMenuItemContentView = nil; UIView *lastPageView = nil; for (int i = 0; i < self.pageItems.count; i++) { MTPageItem *pageItem = self.pageItems[i]; CGRect menuItemContentViewFrame = pageItem.menuItemContentView.frame; menuItemContentViewFrame.origin = CGPointMake(CGRectGetMaxX(lastMenuItemContentView.frame) + (lastMenuItemContentView ? self.config.menuItemSpacing : 0 ), menuItemContentViewFrame.origin.y); pageItem.menuItemContentView.frame = menuItemContentViewFrame; pageItem.menuItemContentView.tag = i; lastMenuItemContentView = pageItem.menuItemContentView; UIView *pageSubview = pageItem.viewController.view; CGRect pageSubviewFrame = pageSubview.frame; pageSubviewFrame.origin = CGPointMake(CGRectGetMaxX(lastPageView.frame), pageSubviewFrame.origin.y); pageSubview.frame = pageSubviewFrame; lastPageView = pageSubview; } _menuContentScrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastMenuItemContentView.frame), 0); _pageContentScrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastPageView.frame), 0); if (self.currentIndex >= self.pageItems.count && self.pageItems.count > 0) { self.currentIndex = self.pageItems.count - 1; } [self selectItemAtIndex:self.currentIndex]; } - (void)menuItemDidTap:(UITapGestureRecognizer *)gr { UIView *menuItemContentView = gr.view; [self selectItemAtIndex:menuItemContentView.tag]; } + (instancetype)pageViewWithItemModels:(NSArray *)itemModels setupConfig:(void (^)(MTPageConfig * _Nonnull))setupConfigBlock itemDidSelect:(void (^)(id _Nonnull, NSInteger))itemDidSelectBlock { MTPageView *pageView = [[self alloc] init]; pageView.itemDidSelectBlock = itemDidSelectBlock; pageView.setupConfigBlock = setupConfigBlock; if (setupConfigBlock) { setupConfigBlock(pageView.config); } pageView.frame = CGRectMake(0, 0, pageView.config.pageWidth, pageView.config.pageHeight); if (itemModels.count > 0) { [pageView setupUIWithItemModels:itemModels]; } return pageView; } - (void)selectItemAtIndex:(NSInteger)index animated:(BOOL)animated { if (index == self.currentIndex || index < 0 || index >= self.pageItems.count) return; /// 选择菜单 [self selectedMenuItemAtIndex:index animated:animated]; /// 滚动到指定pageview [self scrollToPageViewAtIndex:index animated:animated]; } /// 选中指定下标 - (void)selectItemAtIndex:(NSInteger)index { BOOL animated = self.config.switchAnimated; [self selectItemAtIndex:index animated:animated]; } - (UIViewController *)currentPageViewController { return self.currentIndex < self.parentViewController.childViewControllers.count ? self.parentViewController.childViewControllers[self.currentIndex] : nil; } - (void)selectedMenuItemAtIndex:(NSInteger)index animated:(BOOL)animated { NSTimeInterval animatedDuration = animated * self.config.animatedDuration; MTPageItem *lastSelectPageItem = nil; if (self.currentIndex >= 0 && self.currentIndex < self.pageItems.count) { lastSelectPageItem = self.pageItems[self.currentIndex]; } void(^changeMenuItemStateBlock)(void) = ^ { MTPageItem *pageItem = self.pageItems[index]; [UIView animateWithDuration:animatedDuration animations:^{ if (self.config.itemStateChangedBlock) { self.config.itemStateChangedBlock(lastSelectPageItem.menuItemView, lastSelectPageItem.itemModel, MTPageMenuStateNormal); self.config.itemStateChangedBlock(pageItem.menuItemView, pageItem.itemModel, MTPageMenuStateSelected); } self.sliderBar.centerX = pageItem.menuItemView.superview.centerX; }]; if (self.itemDidSelectBlock) { self.itemDidSelectBlock(pageItem.itemModel, index); } CGFloat maxX = CGRectGetMaxX(pageItem.menuItemView.superview.frame) + self.menuContentScrollView.contentInset.right; CGFloat minX = CGRectGetMinX(pageItem.menuItemView.superview.frame) - self.menuContentScrollView.contentInset.left; if (minX < self.menuContentScrollView.contentOffset.x) { [self.menuContentScrollView setContentOffset:CGPointMake(minX, self.menuContentScrollView.contentOffset.y) animated:YES]; } if (maxX > self.menuContentScrollView.contentOffset.x + self.menuContentScrollView.frame.size.width) { [self.menuContentScrollView setContentOffset:CGPointMake(maxX - self.menuContentScrollView.frame.size.width, self.menuContentScrollView.contentOffset.y) animated:YES]; } }; [UIView animateWithDuration:animatedDuration animations:^{ changeMenuItemStateBlock(); }]; self.currentIndex = index; } - (void)scrollToPageViewAtIndex:(NSInteger)index animated:(BOOL)animated { [UIView animateWithDuration:self.config.animatedDuration * animated animations:^{ [self.pageContentScrollView setContentOffset:CGPointMake(self.pageContentScrollView.frame.size.width * index, 0) animated:animated]; }]; [self handlePageViewDiappearOrAppearWhenSelectIndex:index animated:animated]; } - (void)handlePageViewDiappearOrAppearWhenSelectIndex:(NSInteger)index animated:(BOOL)animated { UIViewController *willDisappearVc = self.pageItems[self.currentIndex].viewController; UIViewController *willAppearVc = self.pageItems[index].viewController; [willDisappearVc viewWillDisappear:YES]; [willAppearVc viewWillAppear:YES]; [UIView animateWithDuration:self.config.animatedDuration * animated animations:^{ } completion:^(BOOL finished) { [willDisappearVc viewDidDisappear:YES]; [willAppearVc viewDidAppear:YES]; }]; } /// 对换下标 - (void)exchangeItemAtIndex:(NSInteger)index1 withItemAtIndex:(NSInteger)index2 { [self.pageItems exchangeObjectAtIndex:index1 withObjectAtIndex:index2]; [self relayoutUI]; } /// 移除下标 - (void)removeItemAtIndex:(NSInteger)index { [self.pageItems removeObjectAtIndex:index]; [self relayoutUI]; } - (void)addItemWithModel:(id)itemModel { [self addItemWithModel:itemModel needRelayout:YES]; } - (void)addItemWithModel:(id)itemModel needRelayout:(BOOL)needRelayout { MTPageConfig *config = self.config; UIView *lastMenuItemContentView = self.pageItems.lastObject.menuItemContentView; UIView *lastPageView = self.pageItems.lastObject.viewController.view; MTPageItem *pageItem = [[MTPageItem alloc] init]; pageItem.itemModel = itemModel; if (config.setupMenuItemContentViewBlock) { UIView *menuItemContentView = [[UIView alloc] initWithFrame:CGRectMake(CGRectGetMaxX(lastMenuItemContentView.frame) + (lastMenuItemContentView ? config.menuItemSpacing : 0), 0, config.menuItemSize.width, config.menuItemSize.height)]; UIView *menuItemView = config.setupMenuItemContentViewBlock(menuItemContentView, itemModel); if (!CGSizeEqualToSize(config.menuItemSize, CGSizeZero)) { // 设置item大小 CGRect frame = menuItemView.frame; frame.size = config.menuItemSize; menuItemView.frame = frame; } else { CGRect frame = menuItemContentView.frame; frame.size = menuItemView.frame.size; menuItemContentView.frame = frame; } CGRect menuContentScrollViewFrame = _menuContentScrollView.frame; menuContentScrollViewFrame.size = CGSizeMake(config.menuContentWidth, menuItemContentView.height + config.menuContentInset.top + config.menuContentInset.bottom); _menuContentScrollView.frame = menuContentScrollViewFrame; [menuItemContentView addSubview:menuItemView]; [_menuContentScrollView addSubview:menuItemContentView]; pageItem.menuItemView = menuItemView; pageItem.menuItemContentView = menuItemContentView; menuItemContentView.tag = self.pageItems.count; [menuItemContentView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(menuItemDidTap:)]]; menuItemView.centerY = menuItemContentView.height * 0.5; lastMenuItemContentView = menuItemContentView; } if (config.setupPageContentViewBlock) { UIViewController *vc = config.setupPageContentViewBlock(_pageContentScrollView, itemModel); pageItem.viewController = vc; [self.parentViewController addChildViewController:vc]; UIView *view = vc.view; view.frame = CGRectMake(CGRectGetMaxX(lastPageView.frame), 0, _pageContentScrollView.frame.size.width, _pageContentScrollView.frame.size.height); [_pageContentScrollView addSubview:view]; [vc didMoveToParentViewController:self.parentViewController]; lastPageView = view; } if (self.config.itemStateChangedBlock) { self.config.itemStateChangedBlock(pageItem.menuItemView, itemModel, MTPageMenuStateNormal); } [self.pageItems addObject:pageItem]; _menuContentScrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastMenuItemContentView.frame), 0); _pageContentScrollView.contentSize = CGSizeMake(CGRectGetMaxX(lastPageView.frame), 0); if (needRelayout) { [self relayoutUI]; } } #pragma mark - UIScrollViewDelegate - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { NSInteger index = (*targetContentOffset).x / scrollView.frame.size.width; if (index != self.currentIndex) { [self selectedMenuItemAtIndex:index animated:self.config.switchAnimated]; [self handlePageViewDiappearOrAppearWhenSelectIndex:index animated:self.config.switchAnimated]; } } @end