// // WMPageController.m // WMPageController // // Created by Mark on 15/6/11. // Copyright (c) 2015年 yq. All rights reserved. // #import "WMPageController.h" NSString *const WMControllerDidAddToSuperViewNotification = @"WMControllerDidAddToSuperViewNotification"; NSString *const WMControllerDidFullyDisplayedNotification = @"WMControllerDidFullyDisplayedNotification"; static NSInteger const kWMUndefinedIndex = -1; static NSInteger const kWMControllerCountUndefined = -1; @interface WMPageController () { CGFloat _targetX; CGRect _contentViewFrame, _menuViewFrame; BOOL _hasInited, _shouldNotScroll; NSInteger _initializedIndex, _controllerCount, _markedSelectIndex; } @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)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self wm_setup]; } return self; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { [self wm_setup]; } return self; } - (instancetype)initWithViewControllerClasses:(NSArray *)classes andTheirTitles:(NSArray *)titles { if (self = [self initWithNibName:nil bundle:nil]) { NSParameterAssert(classes.count == titles.count); _viewControllerClasses = [NSArray arrayWithArray:classes]; _titles = [NSArray arrayWithArray:titles]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyAfterMemoryWarning) object:nil]; [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(wm_growCachePolicyToHigh) object:nil]; } - (void)forceLayoutSubviews { if (!self.childControllersCount) return; // 计算宽高及子控制器的视图frame [self wm_calculateSize]; [self wm_adjustScrollViewFrame]; [self wm_adjustMenuViewFrame]; [self wm_adjustDisplayingViewControllersFrame]; } - (void)setScrollEnable:(BOOL)scrollEnable { _scrollEnable = scrollEnable; if (!self.scrollView) return; self.scrollView.scrollEnabled = scrollEnable; } - (void)setProgressViewCornerRadius:(CGFloat)progressViewCornerRadius { _progressViewCornerRadius = progressViewCornerRadius; if (self.menuView) { self.menuView.progressViewCornerRadius = progressViewCornerRadius; } } - (void)setMenuViewLayoutMode:(WMMenuViewLayoutMode)menuViewLayoutMode { _menuViewLayoutMode = menuViewLayoutMode; if (self.menuView.superview) { [self wm_resetMenuView]; } } - (void)setCachePolicy:(WMPageControllerCachePolicy)cachePolicy { _cachePolicy = cachePolicy; if (cachePolicy != WMPageControllerCachePolicyDisabled) { self.memCache.countLimit = _cachePolicy; } } - (void)setSelectIndex:(int)selectIndex { _selectIndex = selectIndex; _markedSelectIndex = kWMUndefinedIndex; if (self.menuView && _hasInited) { [self.menuView selectItemAtIndex:selectIndex]; } else { _markedSelectIndex = selectIndex; UIViewController *vc = [self.memCache objectForKey:@(selectIndex)]; if (!vc) { vc = [self initializeViewControllerAtIndex:selectIndex]; [self.memCache setObject:vc forKey:@(selectIndex)]; } self.currentViewController = vc; } } - (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)reloadData { [self wm_clearDatas]; if (!self.childControllersCount) return; [self wm_resetScrollView]; [self.memCache removeAllObjects]; [self wm_resetMenuView]; [self viewDidLayoutSubviews]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; } - (void)updateTitle:(NSString *)title atIndex:(NSInteger)index { [self.menuView updateTitle:title atIndex:index andWidth:NO]; } - (void)updateAttributeTitle:(NSAttributedString * _Nonnull)title atIndex:(NSInteger)index { [self.menuView updateAttributeTitle: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]; } - (void)setShowOnNavigationBar:(BOOL)showOnNavigationBar { if (_showOnNavigationBar == showOnNavigationBar) { return; } _showOnNavigationBar = showOnNavigationBar; if (self.menuView) { [self.menuView removeFromSuperview]; [self wm_addMenuView]; [self forceLayoutSubviews]; [self.menuView slideMenuAtProgress:self.selectIndex]; } } #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; // Post FullyDisplayedNotification [self wm_postFullyDisplayedNotificationWithCurrentIndex:self.selectIndex]; 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 length = (int)self.preloadPolicy; int start = 0; int end = (int)self.childControllersCount - 1; if (index > length) { start = (int)index - length; } if (self.childControllersCount - 1 > length + index) { end = (int)index + length; } 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 (_controllerCount == kWMControllerCountUndefined) { if ([self.dataSource respondsToSelector:@selector(numbersOfChildControllersInPageController:)]) { _controllerCount = [self.dataSource numbersOfChildControllersInPageController:self]; } else { _controllerCount = self.viewControllerClasses.count; } } return _controllerCount; } - (UIViewController * _Nonnull)initializeViewControllerAtIndex:(NSInteger)index { if ([self.dataSource respondsToSelector:@selector(pageController:viewControllerAtIndex:)]) { return [self.dataSource pageController:self viewControllerAtIndex:index]; } return [[self.viewControllerClasses[index] alloc] init]; } - (NSString * _Nonnull)titleAtIndex:(NSInteger)index { NSString *title = nil; if ([self.dataSource respondsToSelector:@selector(pageController:titleAtIndex:)]) { title = [self.dataSource pageController:self titleAtIndex:index]; } else { title = self.titles[index]; } return (title ?: @""); } #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 { _controllerCount = kWMControllerCountUndefined; _hasInited = NO; NSUInteger maxIndex = (self.childControllersCount - 1 > 0) ? (self.childControllersCount - 1) : 0; _selectIndex = self.selectIndex < self.childControllersCount ? self.selectIndex : (int)maxIndex; if (self.progressWidth > 0) { self.progressWidth = self.progressWidth; } 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:self userInfo:info]; } // 当子控制器完全展示在user面前时发送通知 - (void)wm_postFullyDisplayedNotificationWithCurrentIndex:(int)index { if (!self.postNotification) return; NSDictionary *info = @{ @"index":@(index), @"title":[self titleAtIndex:index] }; [[NSNotificationCenter defaultCenter] postNotificationName:WMControllerDidFullyDisplayedNotification object:self userInfo:info]; } // 初始化一些参数,在init中调用 - (void)wm_setup { _titleSizeSelected = 18.0f; _titleSizeNormal = 15.0f; _titleColorSelected = [UIColor colorWithRed:168.0/255.0 green:20.0/255.0 blue:4/255.0 alpha:1]; _titleColorNormal = [UIColor colorWithRed:0 green:0 blue:0 alpha:1]; _menuItemWidth = 65.0f; _memCache = [[NSCache alloc] init]; _initializedIndex = kWMUndefinedIndex; _markedSelectIndex = kWMUndefinedIndex; _controllerCount = kWMControllerCountUndefined; _scrollEnable = YES; _progressViewCornerRadius = WMUNDEFINED_VALUE; _progressHeight = WMUNDEFINED_VALUE; self.automaticallyCalculatesItemWidths = NO; 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 { _menuViewFrame = [self.dataSource pageController:self preferredFrameForMenuView:self.menuView]; _contentViewFrame = [self.dataSource pageController:self preferredFrameForContentView:self.scrollView]; _childViewFrames = [NSMutableArray array]; for (int i = 0; i < self.childControllersCount; i++) { CGRect frame = CGRectMake(i * _contentViewFrame.size.width, 0, _contentViewFrame.size.width, _contentViewFrame.size.height); [_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.scrollEnabled = self.scrollEnable; if (@available(iOS 11.0, *)) { scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } [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 { WMMenuView *menuView = [[WMMenuView alloc] initWithFrame:CGRectZero]; 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; menuView.progressViewCornerRadius = self.progressViewCornerRadius; menuView.showOnNavigationBar = self.showOnNavigationBar; 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; } - (void)wm_layoutChildViewControllers { int currentPage = (int)(self.scrollView.contentOffset.x / _contentViewFrame.size.width); int length = (int)self.preloadPolicy; int left = currentPage - length - 1; int right = currentPage + length + 1; for (int i = 0; i < self.childControllersCount; i++) { UIViewController *vc = [self.displayVC objectForKey:@(i)]; CGRect frame = [self.childViewFrames[i] CGRectValue]; if (!vc) { if ([self wm_isInScreen:frame]) { [self wm_initializedControllerWithIndexIfNeeded:i]; } } else if (i <= left || i >= right) { if (![self wm_isInScreen:frame]) { [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_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.cachePolicy == WMPageControllerCachePolicyDisabled) { return; } 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.menuView.userInteractionEnabled == NO) { self.menuView.userInteractionEnabled = YES; } 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; CGFloat oldContentOffsetX = self.scrollView.contentOffset.x; CGFloat contentWidth = self.scrollView.contentSize.width; self.scrollView.frame = _contentViewFrame; self.scrollView.contentSize = CGSizeMake(self.childControllersCount * _contentViewFrame.size.width, 0); CGFloat xContentOffset = contentWidth == 0 ? self.selectIndex * _contentViewFrame.size.width : oldContentOffsetX / contentWidth * self.childControllersCount * _contentViewFrame.size.width; [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 { CGFloat oriWidth = self.menuView.frame.size.width; self.menuView.frame = _menuViewFrame; [self.menuView resetFrames]; if (oriWidth != self.menuView.frame.size.width) { [self.menuView refreshContenOffset]; } } - (CGFloat)wm_calculateItemWithAtIndex:(NSInteger)index { NSString *title = [self titleAtIndex:index]; UIFont *titleFont = self.titleFontName ? [UIFont fontWithName:self.titleFontName size:self.titleSizeSelected] : [UIFont systemFontOfSize:self.titleSizeSelected]; NSDictionary *attrs = @{NSFontAttributeName: titleFont}; CGFloat itemWidth = [title boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:attrs context:nil].size.width; return ceil(itemWidth); } - (void)wm_delaySelectIndexIfNeeded { if (_markedSelectIndex != kWMUndefinedIndex) { self.selectIndex = (int)_markedSelectIndex; } } #pragma mark - Life Cycle - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; if (!self.childControllersCount) return; [self wm_calculateSize]; [self wm_addScrollView]; [self wm_initializedControllerWithIndexIfNeeded:self.selectIndex]; self.currentViewController = self.displayVC[@(self.selectIndex)]; [self wm_addMenuView]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; } - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if (!self.childControllersCount) return; [self forceLayoutSubviews]; _hasInited = YES; [self wm_delaySelectIndexIfNeeded]; } - (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 (![scrollView isKindOfClass:WMScrollView.class]) return; if (_shouldNotScroll || !_hasInited) return; [self wm_layoutChildViewControllers]; if (_startDragging) { CGFloat contentOffsetX = scrollView.contentOffset.x; if (contentOffsetX < 0) { contentOffsetX = 0; } if (contentOffsetX > scrollView.contentSize.width - _contentViewFrame.size.width) { contentOffsetX = scrollView.contentSize.width - _contentViewFrame.size.width; } CGFloat rate = contentOffsetX / _contentViewFrame.size.width; [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 { if (![scrollView isKindOfClass:WMScrollView.class]) return; _startDragging = YES; self.menuView.userInteractionEnabled = NO; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if (![scrollView isKindOfClass:WMScrollView.class]) return; self.menuView.userInteractionEnabled = YES; _selectIndex = (int)(scrollView.contentOffset.x / _contentViewFrame.size.width); self.currentViewController = self.displayVC[@(self.selectIndex)]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; [self.menuView deselectedItemsIfNeeded]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { if (![scrollView isKindOfClass:WMScrollView.class]) return; self.currentViewController = self.displayVC[@(self.selectIndex)]; [self didEnterController:self.currentViewController atIndex:self.selectIndex]; [self.menuView deselectedItemsIfNeeded]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (![scrollView isKindOfClass:WMScrollView.class]) return; if (!decelerate) { self.menuView.userInteractionEnabled = YES; CGFloat rate = _targetX / _contentViewFrame.size.width; [self.menuView slideMenuAtProgress:rate]; [self.menuView deselectedItemsIfNeeded]; } } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { if (![scrollView isKindOfClass:WMScrollView.class]) return; _targetX = targetContentOffset->x; } #pragma mark - WMMenuView Delegate - (void)menuView:(WMMenuView *)menu didSelectedIndex:(NSInteger)index currentIndex:(NSInteger)currentIndex { if (!_hasInited) return; _selectIndex = (int)index; _startDragging = NO; CGPoint targetP = CGPointMake(_contentViewFrame.size.width * index, 0); [self.scrollView setContentOffset:targetP animated:self.pageAnimatable]; if (self.pageAnimatable) return; // 由于不触发 -scrollViewDidScroll: 手动处理控制器 UIViewController *currentViewController = self.displayVC[@(currentIndex)]; if (currentViewController) { [self wm_removeViewController:currentViewController atIndex:currentIndex]; } [self wm_layoutChildViewControllers]; self.currentViewController = self.displayVC[@(self.selectIndex)]; [self didEnterController:self.currentViewController atIndex:index]; } - (CGFloat)menuView:(WMMenuView *)menu widthForItemAtIndex:(NSInteger)index { if (self.automaticallyCalculatesItemWidths) { return [self wm_calculateItemWithAtIndex: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 atIndex:(NSInteger)index { switch (state) { case WMMenuItemStateSelected: return self.titleSizeSelected; case WMMenuItemStateNormal: return self.titleSizeNormal; } } - (UIColor *)menuView:(WMMenuView *)menu titleColorForState:(WMMenuItemState)state atIndex:(NSInteger)index { switch (state) { case WMMenuItemStateSelected: return self.titleColorSelected; case WMMenuItemStateNormal: return self.titleColorNormal; } } #pragma mark - WMMenuViewDataSource - (NSInteger)numbersOfTitlesInMenuView:(WMMenuView *)menu { return self.childControllersCount; } - (NSString *)menuView:(WMMenuView *)menu titleAtIndex:(NSInteger)index { return [self titleAtIndex:index]; } #pragma mark - WMPageControllerDataSource - (CGRect)pageController:(WMPageController *)pageController preferredFrameForMenuView:(WMMenuView *)menuView { NSAssert(0, @"[%@] MUST IMPLEMENT DATASOURCE METHOD `-pageController:preferredFrameForMenuView:`", [self.dataSource class]); return CGRectZero; } - (CGRect)pageController:(WMPageController *)pageController preferredFrameForContentView:(WMScrollView *)contentView { NSAssert(0, @"[%@] MUST IMPLEMENT DATASOURCE METHOD `-pageController:preferredFrameForContentView:`", [self.dataSource class]); return CGRectZero; } @end