491 lines
17 KiB
Objective-C
491 lines
17 KiB
Objective-C
//
|
|
// 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 () <UIScrollViewDelegate>
|
|
|
|
@property (nonatomic, strong) MTPageConfig *config;
|
|
|
|
@property (nonatomic, strong) NSMutableArray <MTPageItem *> *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
|