// // WMPageController.m // WMPageController // // Created by Mark on 15/6/11. // Copyright (c) 2015年 yq. All rights reserved. // #import "WMPageController.h" #import "WMPageConst.h" static NSInteger const kWMUndefinedIndex = -1; static NSInteger const kWMControllerCountUndefined = -1; @interface WMPageController () { CGFloat _viewHeight, _viewWidth, _viewX, _viewY, _targetX, _superviewHeight; BOOL _hasInited, _shouldNotScroll; NSInteger _initializedIndex, _controllerConut; } @property (nonatomic, strong, readwrite) UIViewController *currentViewController; // 用于记录子控制器view的frame,用于 scrollView 上的展示的位置 @property (nonatomic, strong) NSMutableArray *childViewFrames; // 当前展示在屏幕上的控制器,方便在滚动的时候读取 (避免不必要计算) @property (nonatomic, strong) NSMutableDictionary *displayVC; // 用于记录销毁的viewController的位置 (如果它是某一种scrollView的Controller的话) @property (nonatomic, strong) NSMutableDictionary *posRecords; // 用于缓存加载过的控制器 @property (nonatomic, strong) NSCache *memCache; @property (nonatomic, strong) NSMutableDictionary *backgroundCache; // 收到内存警告的次数 @property (nonatomic, assign) int memoryWarningCount; @property (nonatomic, readonly) NSInteger childControllersCount; @end @implementation WMPageController #pragma mark - Lazy Loading - (NSMutableDictionary *)posRecords { if (_posRecords == nil) { _posRecords = [[NSMutableDictionary alloc] init]; } return _posRecords; } - (NSMutableDictionary *)displayVC { if (_displayVC == nil) { _displayVC = [[NSMutableDictionary alloc] init]; } return _displayVC; } - (NSMutableDictionary *)backgroundCache { if (_backgroundCache == nil) { _backgroundCache = [[NSMutableDictionary alloc] init]; } return _backgroundCache; } #pragma mark - Public Methods - (instancetype)initWithViewControllerClasses:(NSArray *)classes andTheirTitles:(NSArray *)titles { if (self = [super init]) { NSParameterAssert(classes.count == titles.count); _viewControllerClasses = [NSArray arrayWithArray:classes]; _titles = [NSArray arrayWithArray:titles]; [self wm_setup]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self wm_setup]; } return self; } - (instancetype)init { if (self = [super init]) { [self wm_setup]; } return self; } - (void)setEdgesForExtendedLayout:(UIRectEdge)edgesForExtendedLayout { [super setEdgesForExtendedLayout:edgesForExtendedLayout]; if (_hasInited) { _hasInited = NO; [self viewDidLayoutSubviews]; } } - (void)setMenuViewLayoutMode:(WMMenuViewLayoutMode)menuViewLayoutMode { _menuViewLayoutMode = menuViewLayoutMode; if (self.menuView.superview) { [self wm_resetMenuView]; } } - (void)setCachePolicy:(WMPageControllerCachePolicy)cachePolicy { _cachePolicy = cachePolicy; self.memCache.countLimit = _cachePolicy; } - (void)setSelectIndex:(int)selectIndex { _selectIndex = selectIndex; if (self.menuView) { [self.menuView selectItemAtIndex:selectIndex]; } } - (void)setProgressViewIsNaughty:(BOOL)progressViewIsNaughty { _progressViewIsNaughty = progressViewIsNaughty; if (self.menuView) { self.menuView.progressViewIsNaughty = progressViewIsNaughty; } } - (void)setProgressWidth:(CGFloat)progressWidth { _progressWidth = progressWidth; self.progressViewWidths = ({ NSMutableArray *tmp = [NSMutableArray array]; for (int i = 0; i < self.childControllersCount; i++) { [tmp addObject:@(progressWidth)]; } tmp.copy; }); } - (void)setProgressViewWidths:(NSArray *)progressViewWidths { _progressViewWidths = progressViewWidths; if (self.menuView) { self.menuView.progressWidths = progressViewWidths; } } - (void)setMenuViewContentMargin:(CGFloat)menuViewContentMargin { _menuViewContentMargin = menuViewContentMargin; if (self.menuView) { self.menuView.contentMargin = menuViewContentMargin; } } - (void)setViewFrame:(CGRect)viewFrame { if (CGRectEqualToRect(viewFrame, _viewFrame)) { return; } _viewFrame = viewFrame; if (self.menuView) { _hasInited = NO; [self viewDidLayoutSubviews]; } } - (void)reloadData { [self wm_clearDatas]; if (!self.childControllersCount) { return; } [self wm_resetScrollView]; [self.memCache removeAllObjects]; [self wm_resetMenuView]; [self viewDidLayoutSubviews]; } - (void)updateTitle:(NSString *)title atIndex:(NSInteger)index { [self.menuView updateTitle:title atIndex:index andWidth:NO]; } - (void)updateTitle:(NSString *)title andWidth:(CGFloat)width atIndex:(NSInteger)index { if (self.itemsWidths && index < self.itemsWidths.count) { NSMutableArray *mutableWidths = [NSMutableArray arrayWithArray:self.itemsWidths]; mutableWidths[index] = @(width); self.itemsWidths = [mutableWidths copy]; } else { NSMutableArray *mutableWidths = [NSMutableArray array]; for (int i = 0; i < self.childControllersCount; i++) { CGFloat itemWidth = (i == index) ? width : self.menuItemWidth; [mutableWidths addObject:@(itemWidth)]; } self.itemsWidths = [mutableWidths copy]; } [self.menuView updateTitle:title atIndex:index andWidth:YES]; } #pragma mark - Notification - (void)willResignActive:(NSNotification *)notification { for (int i = 0; i < self.childControllersCount; i++) { id obj = [self.memCache objectForKey:@(i)]; if (obj) { [self.backgroundCache setObject:obj forKey:@(i)]; } } } - (void)willEnterForeground:(NSNotification *)notification { for (NSNumber *key in self.backgroundCache.allKeys) { if (![self.memCache objectForKey:key]) { [self.memCache setObject:self.backgroundCache[key] forKey:key]; } } [self.backgroundCache removeAllObjects]; } #pragma mark - Delegate - (NSDictionary *)infoWithIndex:(NSInteger)index { NSString *title = [self titleAtIndex:index]; return @{@"title": title, @"index": @(index)}; } - (void)willCachedController:(UIViewController *)vc atIndex:(NSInteger)index { if (self.childControllersCount && [self.delegate respondsToSelector:@selector(pageController:willCachedViewController:withInfo:)]) { NSDictionary *info = [self infoWithIndex:index]; [self.delegate pageController:self willCachedViewController:vc withInfo:info]; } } - (void)willEnterController:(UIViewController *)vc atIndex:(NSInteger)index { _selectIndex = (int)index; if (self.childControllersCount && [self.delegate respondsToSelector:@selector(pageController:willEnterViewController:withInfo:)]) { NSDictionary *info = [self infoWithIndex:index]; [self.delegate pageController:self willEnterViewController:vc withInfo:info]; } } // 完全进入控制器 (即停止滑动后调用) - (void)didEnterController:(UIViewController *)vc atIndex:(NSInteger)index { if (!self.childControllersCount) { return; } NSDictionary *info = [self infoWithIndex:index]; if ([self.delegate respondsToSelector:@selector(pageController:didEnterViewController:withInfo:)]) { [self.delegate pageController:self didEnterViewController:vc withInfo:info]; } // 当控制器创建时,调用延迟加载的代理方法 if (_initializedIndex == index && [self.delegate respondsToSelector:@selector(pageController:lazyLoadViewController:withInfo:)]) { [self.delegate pageController:self lazyLoadViewController:vc withInfo:info]; _initializedIndex = kWMUndefinedIndex; } // 根据 preloadPolicy 预加载控制器 if (self.preloadPolicy == WMPageControllerPreloadPolicyNever) { return; } int start = 0; int end = (int)self.childControllersCount - 1; if (index > self.preloadPolicy) { start = (int)index - self.preloadPolicy; } if (self.childControllersCount - 1 > self.preloadPolicy + index) { end = (int)index + self.preloadPolicy; } for (int i = start; i <= end; i++) { // 如果已存在,不需要预加载 if (![self.memCache objectForKey:@(i)] && !self.displayVC[@(i)]) { [self wm_addViewControllerAtIndex:i]; [self wm_postAddToSuperViewNotificationWithIndex:i]; } } _selectIndex = (int)index; } #pragma mark - Data source - (NSInteger)childControllersCount { if (_controllerConut == kWMControllerCountUndefined) { if ([self.dataSource respondsToSelector:@selector(numbersOfChildControllersInPageController:)]) { _controllerConut = [self.dataSource numbersOfChildControllersInPageController:self]; } else { _controllerConut = self.viewControllerClasses.count; } } return _controllerConut; } /* 素素修改 */ - (UIViewController *)initializeViewControllerAtIndex:(NSInteger)index { if ([self.dataSource respondsToSelector:@selector(pageController:viewControllerAtIndex:)]) { return [self.dataSource pageController:self viewControllerAtIndex:index]; } return (UIViewController *)self.viewControllerClasses[index]; } - (NSString *)titleAtIndex:(NSInteger)index { if ([self.dataSource respondsToSelector:@selector(pageController:titleAtIndex:)]) { return [self.dataSource pageController:self titleAtIndex:index]; } return self.titles[index]; } #pragma mark - Private Methods - (void)wm_resetScrollView { if (self.scrollView) { [self.scrollView removeFromSuperview]; } [self wm_addScrollView]; [self wm_addViewControllerAtIndex:self.selectIndex]; self.currentViewController = self.displayVC[@(self.selectIndex)]; } - (void)wm_clearDatas { _controllerConut = kWMControllerCountUndefined; _hasInited = NO; _selectIndex = self.selectIndex < self.childControllersCount ? self.selectIndex : (int)self.childControllersCount - 1; NSArray *displayingViewControllers = self.displayVC.allValues; for (UIViewController *vc in displayingViewControllers) { [vc.view removeFromSuperview]; [vc willMoveToParentViewController:nil]; [vc removeFromParentViewController]; } self.memoryWarningCount = 0; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyAfterMemoryWarning) object:nil]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyToHigh) object:nil]; self.currentViewController = nil; [self.posRecords removeAllObjects]; [self.displayVC removeAllObjects]; } // 当子控制器init完成时发送通知 - (void)wm_postAddToSuperViewNotificationWithIndex:(int)index { if (!self.postNotification) { return; } NSDictionary *info = @{ @"index":@(index), @"title":[self titleAtIndex:index] }; [[NSNotificationCenter defaultCenter] postNotificationName:WMControllerDidAddToSuperViewNotification object:info]; } // 当子控制器完全展示在user面前时发送通知 - (void)wm_postFullyDisplayedNotificationWithCurrentIndex:(int)index { if (!self.postNotification) { return; } NSDictionary *info = @{ @"index":@(index), @"title":[self titleAtIndex:index] }; [[NSNotificationCenter defaultCenter] postNotificationName:WMControllerDidFullyDisplayedNotification object:info]; } // 初始化一些参数,在init中调用 - (void)wm_setup { _titleSizeSelected = WMTitleSizeSelected; _titleColorSelected = NewButtonColor; _titleSizeNormal = WMTitleSizeNormal; _titleColorNormal = NewBlackColor; _menuBGColor = WMMenuBGColor; _menuHeight = WMMenuHeight; _menuItemWidth = WMMenuItemWidth; _memCache = [[NSCache alloc] init]; _initializedIndex = kWMUndefinedIndex; _controllerConut = kWMControllerCountUndefined; self.automaticallyAdjustsScrollViewInsets = NO; self.preloadPolicy = WMPageControllerPreloadPolicyNever; self.cachePolicy = WMPageControllerCachePolicyNoLimit; self.delegate = self; self.dataSource = self; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; } // 包括宽高,子控制器视图 frame - (void)wm_calculateSize { CGFloat navigationHeight = CGRectGetMaxY(self.navigationController.navigationBar.frame); UIView *tabBar = self.tabBarController.tabBar ? self.tabBarController.tabBar : self.navigationController.toolbar; CGFloat height = (tabBar && !tabBar.hidden) ? CGRectGetHeight(tabBar.frame) : 0; CGFloat tarBarHeight = self.hidesBottomBarWhenPushed == YES ? 0 : height; // 计算相对 window 的绝对 frame (self.view.window 可能为 nil) UIWindow *mainWindow = [[UIApplication sharedApplication].delegate window]; CGRect absoluteRect = [self.view.superview convertRect:self.view.frame toView:mainWindow]; navigationHeight -= absoluteRect.origin.y; tarBarHeight -= mainWindow.frame.size.height - CGRectGetMaxY(absoluteRect); _viewX = self.viewFrame.origin.x; _viewY = self.viewFrame.origin.y; if (CGRectEqualToRect(self.viewFrame, CGRectZero)) { _viewWidth = self.view.frame.size.width; _viewHeight = self.view.frame.size.height - self.menuHeight - self.menuViewBottomSpace - navigationHeight - tarBarHeight; _viewY += navigationHeight; } else { _viewWidth = self.viewFrame.size.width; _viewHeight = self.viewFrame.size.height - self.menuHeight - self.menuViewBottomSpace; } if (self.showOnNavigationBar && self.navigationController.navigationBar) { _viewHeight += self.menuHeight; } // 重新计算各个控制器视图的宽高 _childViewFrames = [NSMutableArray array]; for (int i = 0; i < self.childControllersCount; i++) { CGRect frame = CGRectMake(i*_viewWidth, 0, _viewWidth, _viewHeight); [_childViewFrames addObject:[NSValue valueWithCGRect:frame]]; } } - (void)wm_addScrollView { WMScrollView *scrollView = [[WMScrollView alloc] init]; scrollView.scrollsToTop = NO; scrollView.pagingEnabled = YES; scrollView.backgroundColor = [UIColor whiteColor]; scrollView.delegate = self; scrollView.showsVerticalScrollIndicator = NO; scrollView.showsHorizontalScrollIndicator = NO; scrollView.bounces = self.bounces; scrollView.otherGestureRecognizerSimultaneously = self.otherGestureRecognizerSimultaneously; [self.view addSubview:scrollView]; self.scrollView = scrollView; if (!self.navigationController) { return; } for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) { [gestureRecognizer requireGestureRecognizerToFail:self.navigationController.interactivePopGestureRecognizer]; } } - (void)wm_addMenuView { CGFloat menuY = _viewY; if (self.showOnNavigationBar && self.navigationController.navigationBar) { CGFloat navHeight = self.navigationController.navigationBar.frame.size.height; CGFloat menuHeight = self.menuHeight > navHeight ? navHeight : self.menuHeight; menuY = (navHeight - menuHeight) / 2; } CGRect frame = CGRectMake(_viewX, menuY, _viewWidth, self.menuHeight); WMMenuView *menuView = [[WMMenuView alloc] initWithFrame:frame]; menuView.backgroundColor = self.menuBGColor; menuView.delegate = self; menuView.dataSource = self; menuView.style = self.menuViewStyle; menuView.layoutMode = self.menuViewLayoutMode; menuView.progressHeight = self.progressHeight; menuView.contentMargin = self.menuViewContentMargin; menuView.progressViewBottomSpace = self.progressViewBottomSpace; menuView.progressWidths = self.progressViewWidths; menuView.progressViewIsNaughty = self.progressViewIsNaughty; if (self.titleFontName) { menuView.fontName = self.titleFontName; } if (self.progressColor) { menuView.lineColor = self.progressColor; } if (self.showOnNavigationBar && self.navigationController.navigationBar) { self.navigationItem.titleView = menuView; } else { [self.view addSubview:menuView]; } self.menuView = menuView; if (self.selectIndex != 0) { [self.menuView selectItemAtIndex:self.selectIndex]; } } - (void)wm_layoutChildViewControllers { int currentPage = (int)self.scrollView.contentOffset.x / _viewWidth; int start = currentPage == 0 ? currentPage : (currentPage - 1); int end = (currentPage == self.childControllersCount - 1) ? currentPage : (currentPage + 1); for (int i = start; i <= end; i++) { CGRect frame = [self.childViewFrames[i] CGRectValue]; UIViewController *vc = [self.displayVC objectForKey:@(i)]; if ([self wm_isInScreen:frame]) { if (vc == nil) { [self wm_initializedControllerWithIndexIfNeeded:i]; } } else { if (vc) { // vc不在视野中且存在,移除他 [self wm_removeViewController:vc atIndex:i]; } } } } // 创建或从缓存中获取控制器并添加到视图上 - (void)wm_initializedControllerWithIndexIfNeeded:(NSInteger)index { // 先从 cache 中取 UIViewController *vc = [self.memCache objectForKey:@(index)]; if (vc) { // cache 中存在,添加到 scrollView 上,并放入display [self wm_addCachedViewController:vc atIndex:index]; } else { // cache 中也不存在,创建并添加到display [self wm_addViewControllerAtIndex:(int)index]; } [self wm_postAddToSuperViewNotificationWithIndex:(int)index]; } - (void)wm_removeSuperfluousViewControllersIfNeeded { [self.displayVC enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, UIViewController * _Nonnull vc, BOOL * _Nonnull stop) { NSInteger index = key.integerValue; CGRect frame = [self.childViewFrames[index] CGRectValue]; if (![self wm_isInScreen:frame]) { [self wm_removeViewController:vc atIndex:index]; } }]; } - (void)wm_addCachedViewController:(UIViewController *)viewController atIndex:(NSInteger)index { [self addChildViewController:viewController]; viewController.view.frame = [self.childViewFrames[index] CGRectValue]; [viewController didMoveToParentViewController:self]; [self.scrollView addSubview:viewController.view]; [self willEnterController:viewController atIndex:index]; [self.displayVC setObject:viewController forKey:@(index)]; } // 创建并添加子控制器 - (void)wm_addViewControllerAtIndex:(int)index { _initializedIndex = index; UIViewController *viewController = [self initializeViewControllerAtIndex:index]; if (self.values.count == self.childControllersCount && self.keys.count == self.childControllersCount) { [viewController setValue:self.values[index] forKey:self.keys[index]]; } [self addChildViewController:viewController]; CGRect frame = self.childViewFrames.count ? [self.childViewFrames[index] CGRectValue] : self.view.frame; viewController.view.frame = frame; [viewController didMoveToParentViewController:self]; [self.scrollView addSubview:viewController.view]; [self willEnterController:viewController atIndex:index]; [self.displayVC setObject:viewController forKey:@(index)]; [self wm_backToPositionIfNeeded:viewController atIndex:index]; } // 移除控制器,且从display中移除 - (void)wm_removeViewController:(UIViewController *)viewController atIndex:(NSInteger)index { [self wm_rememberPositionIfNeeded:viewController atIndex:index]; [viewController.view removeFromSuperview]; [viewController willMoveToParentViewController:nil]; [viewController removeFromParentViewController]; [self.displayVC removeObjectForKey:@(index)]; // 放入缓存 if (![self.memCache objectForKey:@(index)]) { [self willCachedController:viewController atIndex:index]; [self.memCache setObject:viewController forKey:@(index)]; } } - (void)wm_backToPositionIfNeeded:(UIViewController *)controller atIndex:(NSInteger)index { #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wdeprecated-declarations" if (!self.rememberLocation) return; #pragma clang diagnostic pop if ([self.memCache objectForKey:@(index)]) return; UIScrollView *scrollView = [self wm_isKindOfScrollViewController:controller]; if (scrollView) { NSValue *pointValue = self.posRecords[@(index)]; if (pointValue) { CGPoint pos = [pointValue CGPointValue]; [scrollView setContentOffset:pos]; } } } - (void)wm_rememberPositionIfNeeded:(UIViewController *)controller atIndex:(NSInteger)index { #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wdeprecated-declarations" if (!self.rememberLocation) return; #pragma clang diagnostic pop UIScrollView *scrollView = [self wm_isKindOfScrollViewController:controller]; if (scrollView) { CGPoint pos = scrollView.contentOffset; self.posRecords[@(index)] = [NSValue valueWithCGPoint:pos]; } } - (UIScrollView *)wm_isKindOfScrollViewController:(UIViewController *)controller { UIScrollView *scrollView = nil; if ([controller.view isKindOfClass:[UIScrollView class]]) { // Controller的view是scrollView的子类(UITableViewController/UIViewController替换view为scrollView) scrollView = (UIScrollView *)controller.view; } else if (controller.view.subviews.count >= 1) { // Controller的view的subViews[0]存在且是scrollView的子类,并且frame等与view得frame(UICollectionViewController/UIViewController添加UIScrollView) UIView *view = controller.view.subviews[0]; if ([view isKindOfClass:[UIScrollView class]]) { scrollView = (UIScrollView *)view; } } return scrollView; } - (BOOL)wm_isInScreen:(CGRect)frame { CGFloat x = frame.origin.x; CGFloat ScreenWidth = self.scrollView.frame.size.width; CGFloat contentOffsetX = self.scrollView.contentOffset.x; if (CGRectGetMaxX(frame) > contentOffsetX && x-contentOffsetX < ScreenWidth) { return YES; } else { return NO; } } - (void)wm_resetMenuView { if (!self.menuView) { [self wm_addMenuView]; } else { [self.menuView reload]; if (self.selectIndex != 0) { [self.menuView selectItemAtIndex:self.selectIndex]; } [self.view bringSubviewToFront:self.menuView]; } } - (void)wm_growCachePolicyAfterMemoryWarning { self.cachePolicy = WMPageControllerCachePolicyBalanced; [self performSelector:@selector(wm_growCachePolicyToHigh) withObject:nil afterDelay:2.0 inModes:@[NSRunLoopCommonModes]]; } - (void)wm_growCachePolicyToHigh { self.cachePolicy = WMPageControllerCachePolicyHigh; } #pragma mark - Adjust Frame - (void)wm_adjustScrollViewFrame { // While rotate at last page, set scroll frame will call `-scrollViewDidScroll:` delegate // It's not my expectation, so I use `_shouldNotScroll` to lock it. // Wait for a better solution. _shouldNotScroll = YES; CGRect scrollFrame = CGRectMake(_viewX, _viewY + self.menuHeight + self.menuViewBottomSpace, _viewWidth, _viewHeight); CGFloat oldContentOffsetX = self.scrollView.contentOffset.x; CGFloat contentWidth = self.scrollView.contentSize.width; scrollFrame.origin.y -= self.showOnNavigationBar && self.navigationController.navigationBar ? self.menuHeight : 0; self.scrollView.frame = scrollFrame; self.scrollView.contentSize = CGSizeMake(self.childControllersCount * _viewWidth, 0); CGFloat xContentOffset = contentWidth == 0 ? self.selectIndex * _viewWidth : oldContentOffsetX / contentWidth * self.childControllersCount * _viewWidth; [self.scrollView setContentOffset:CGPointMake(xContentOffset, 0)]; _shouldNotScroll = NO; } - (void)wm_adjustDisplayingViewControllersFrame { [self.displayVC enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, UIViewController * _Nonnull vc, BOOL * _Nonnull stop) { NSInteger index = key.integerValue; CGRect frame = [self.childViewFrames[index] CGRectValue]; vc.view.frame = frame; }]; } - (void)wm_adjustMenuViewFrame { // 根据是否在导航栏上展示调整frame CGFloat menuHeight = self.menuHeight; __block CGFloat menuX = _viewX; __block CGFloat menuY = _viewY; __block CGFloat rightWidth = 0; if (self.showOnNavigationBar && self.navigationController.navigationBar) { [self.navigationController.navigationBar.subviews enumerateObjectsUsingBlock:^(UIView* obj, NSUInteger idx, BOOL *stop) { if (![obj isKindOfClass:[WMMenuView class]] && ![obj isKindOfClass:NSClassFromString(@"_UINavigationBarBackground")] && obj.alpha != 0 && obj.hidden == NO) { CGFloat maxX = CGRectGetMaxX(obj.frame); if (maxX < _viewWidth / 2) { CGFloat leftWidth = maxX; menuX = menuX > leftWidth ? menuX : leftWidth; } CGFloat minX = CGRectGetMinX(obj.frame); if (minX > _viewWidth / 2) { CGFloat width = (_viewWidth - minX); rightWidth = rightWidth > width ? rightWidth : width; } } }]; CGFloat navHeight = self.navigationController.navigationBar.frame.size.height; menuHeight = self.menuHeight > navHeight ? navHeight : self.menuHeight; menuY = (navHeight - menuHeight) / 2; } CGFloat menuWidth = _viewWidth - menuX - rightWidth; self.menuView.frame = CGRectMake(menuX, menuY, menuWidth, menuHeight); [self.menuView resetFrames]; } #pragma mark - Life Cycle - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; if (!self.childControllersCount) return; [self wm_addScrollView]; [self wm_addViewControllerAtIndex:self.selectIndex]; self.currentViewController = self.displayVC[@(self.selectIndex)]; [self wm_addMenuView]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if (!self.childControllersCount) return; CGFloat oldSuperviewHeight = _superviewHeight; _superviewHeight = self.view.frame.size.height; if ((_hasInited && _superviewHeight == oldSuperviewHeight) || !self.view.window) return; // 计算宽高及子控制器的视图frame [self wm_calculateSize]; [self wm_adjustScrollViewFrame]; [self wm_adjustMenuViewFrame]; [self wm_adjustDisplayingViewControllersFrame]; [self wm_removeSuperfluousViewControllersIfNeeded]; _hasInited = YES; [self.view layoutIfNeeded]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (!self.childControllersCount) { return; } [self wm_postFullyDisplayedNotificationWithCurrentIndex:self.selectIndex]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. self.memoryWarningCount++; self.cachePolicy = WMPageControllerCachePolicyLowMemory; // 取消正在增长的 cache 操作 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyAfterMemoryWarning) object:nil]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyToHigh) object:nil]; [self.memCache removeAllObjects]; [self.posRecords removeAllObjects]; self.posRecords = nil; // 如果收到内存警告次数小于 3,一段时间后切换到模式 Balanced if (self.memoryWarningCount < 3) { [self performSelector:@selector(wm_growCachePolicyAfterMemoryWarning) withObject:nil afterDelay:3.0 inModes:@[NSRunLoopCommonModes]]; } } #pragma mark - UIScrollView Delegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (_shouldNotScroll || !_hasInited) { return; } [self wm_layoutChildViewControllers]; if (_startDragging) { CGFloat contentOffsetX = scrollView.contentOffset.x; if (contentOffsetX < 0) { contentOffsetX = 0; } if (contentOffsetX > scrollView.contentSize.width - _viewWidth) { contentOffsetX = scrollView.contentSize.width - _viewWidth; } CGFloat rate = contentOffsetX / _viewWidth; [self.menuView slideMenuAtProgress:rate]; } // fix scrollView.contentOffset.y -> (-20) unexpectedly. if (scrollView.contentOffset.y == 0) { return; } CGPoint contentOffset = scrollView.contentOffset; contentOffset.y = 0.0; scrollView.contentOffset = contentOffset; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { _startDragging = YES; self.menuView.userInteractionEnabled = NO; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { self.menuView.userInteractionEnabled = YES; _selectIndex = (int)scrollView.contentOffset.x / _viewWidth; [self wm_removeSuperfluousViewControllersIfNeeded]; self.currentViewController = self.displayVC[@(self.selectIndex)]; [self wm_postFullyDisplayedNotificationWithCurrentIndex:self.selectIndex]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { self.currentViewController = self.displayVC[@(self.selectIndex)]; [self wm_removeSuperfluousViewControllersIfNeeded]; [self wm_postFullyDisplayedNotificationWithCurrentIndex:self.selectIndex]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { self.menuView.userInteractionEnabled = YES; CGFloat rate = _targetX / _viewWidth; [self.menuView slideMenuAtProgress:rate]; } } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { _targetX = targetContentOffset->x; } #pragma mark - WMMenuView Delegate - (void)menuView:(WMMenuView *)menu didSelesctedIndex:(NSInteger)index currentIndex:(NSInteger)currentIndex { if (!_hasInited) { return; } _selectIndex = (int)index; _startDragging = NO; CGPoint targetP = CGPointMake(_viewWidth*index, 0); [self.scrollView setContentOffset:targetP animated:self.pageAnimatable]; if (!self.pageAnimatable) { // 由于不触发 -scrollViewDidScroll: 手动处理控制器 [self wm_removeSuperfluousViewControllersIfNeeded]; UIViewController *currentViewController = self.displayVC[@(currentIndex)]; if (currentViewController) { [self wm_removeViewController:currentViewController atIndex:currentIndex]; } [self wm_layoutChildViewControllers]; self.currentViewController = self.displayVC[@(self.selectIndex)]; [self wm_postFullyDisplayedNotificationWithCurrentIndex:(int)index]; [self didEnterController:self.currentViewController atIndex:index]; } } - (CGFloat)menuView:(WMMenuView *)menu widthForItemAtIndex:(NSInteger)index { if (self.itemsWidths.count == self.childControllersCount) { return [self.itemsWidths[index] floatValue]; } return self.menuItemWidth; } - (CGFloat)menuView:(WMMenuView *)menu itemMarginAtIndex:(NSInteger)index { if (self.itemsMargins.count == self.childControllersCount + 1) { return [self.itemsMargins[index] floatValue]; } return self.itemMargin; } - (CGFloat)menuView:(WMMenuView *)menu titleSizeForState:(WMMenuItemState)state { switch (state) { case WMMenuItemStateSelected: { return self.titleSizeSelected; break; } case WMMenuItemStateNormal: { return self.titleSizeNormal; break; } } } - (UIColor *)menuView:(WMMenuView *)menu titleColorForState:(WMMenuItemState)state { switch (state) { case WMMenuItemStateSelected: { return self.titleColorSelected; break; } case WMMenuItemStateNormal: { return self.titleColorNormal; break; } } } #pragma mark - WMMenuViewDataSource - (NSInteger)numbersOfTitlesInMenuView:(WMMenuView *)menu { return self.childControllersCount; } - (NSString *)menuView:(WMMenuView *)menu titleAtIndex:(NSInteger)index { return [self titleAtIndex:index]; } @end