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