| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871 |
- //
- // 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<Class> *)classes andTheirTitles:(NSArray<NSString *> *)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
|