// // CYLTabBarController.m // CYLTabBarController // // v1.16.0 Created by 微博@iOS程序犭袁 ( http://weibo.com/luohanchenyilong/ ) on 10/20/15. // Copyright © 2015 https://github.com/ChenYilong . All rights reserved. // #import "CYLTabBarController.h" #import "CYLTabBar.h" #import #import "UIViewController+CYLTabBarControllerExtention.h" NSString *const CYLTabBarItemTitle = @"CYLTabBarItemTitle"; NSString *const CYLTabBarItemImage = @"CYLTabBarItemImage"; NSString *const CYLTabBarItemSelectedImage = @"CYLTabBarItemSelectedImage"; NSString *const CYLTabBarItemImageInsets = @"CYLTabBarItemImageInsets"; NSString *const CYLTabBarItemTitlePositionAdjustment = @"CYLTabBarItemTitlePositionAdjustment"; NSUInteger CYLTabbarItemsCount = 0; NSUInteger CYLPlusButtonIndex = 0; CGFloat CYLTabBarItemWidth = 0.0f; NSString *const CYLTabBarItemWidthDidChangeNotification = @"CYLTabBarItemWidthDidChangeNotification"; static void * const CYLTabImageViewDefaultOffsetContext = (void*)&CYLTabImageViewDefaultOffsetContext; @interface CYLTabBarController () @property (nonatomic, assign, getter=isObservingTabImageViewDefaultOffset) BOOL observingTabImageViewDefaultOffset; @end @implementation CYLTabBarController @synthesize viewControllers = _viewControllers; #pragma mark - #pragma mark - Life Cycle - (void)viewDidLoad { [super viewDidLoad]; if (CYL_IS_IPHONE_X) { self.tabBarHeight = 83; } // 处理tabBar,使用自定义 tabBar 添加 发布按钮 [self setUpTabBar]; // KVO注册监听 if (!self.isObservingTabImageViewDefaultOffset) { [self.tabBar addObserver:self forKeyPath:@"tabImageViewDefaultOffset" options:NSKeyValueObservingOptionNew context:CYLTabImageViewDefaultOffsetContext]; self.observingTabImageViewDefaultOffset = YES; } } - (void)setViewDidLayoutSubViewsBlock:(CYLViewDidLayoutSubViewsBlock)viewDidLayoutSubviewsBlock { _viewDidLayoutSubviewsBlock = viewDidLayoutSubviewsBlock; } - (void)viewDidLayoutSubviews { CGFloat deviceVersion = [[[UIDevice currentDevice] systemVersion] floatValue]; if (deviceVersion >= 10 && deviceVersion < 10.2) { [self.tabBar layoutSubviews];//Fix issue #93 } UITabBar *tabBar = self.tabBar; for (UIControl *control in tabBar.subviews) { if ([control isKindOfClass:[UIControl class]]) { SEL actin = @selector(didSelectControl:); [control addTarget:self action:actin forControlEvents:UIControlEventTouchUpInside]; } } !self.viewDidLayoutSubviewsBlock ?: self.viewDidLayoutSubviewsBlock(self); } - (void)viewWillLayoutSubviews { if (!(self.tabBarHeight > 0)) { return; } self.tabBar.frame = ({ CGRect frame = self.tabBar.frame; CGFloat tabBarHeight = self.tabBarHeight; frame.size.height = tabBarHeight; frame.origin.y = self.view.frame.size.height - tabBarHeight; frame; }); } - (UIInterfaceOrientationMask)supportedInterfaceOrientations { UIViewController *controller = self.selectedViewController; if ([controller isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)controller; return navigationController.topViewController.supportedInterfaceOrientations; } else { return controller.supportedInterfaceOrientations; } } - (void)dealloc { UIButton *plusButton = CYLExternPlusButton; if (plusButton.superview && (plusButton.superview == self.tabBar)) { plusButton.selected = NO; [plusButton removeFromSuperview]; } BOOL isAdded = [self isPlusViewControllerAdded:_viewControllers]; BOOL hasPlusChildViewController = [self hasPlusChildViewController] && isAdded; if (isAdded && hasPlusChildViewController && CYLPlusChildViewController.cyl_plusViewControllerEverAdded == YES) { [CYLPlusChildViewController cyl_setPlusViewControllerEverAdded:NO]; } // KVO反注册 if (self.isObservingTabImageViewDefaultOffset) { [self.tabBar removeObserver:self forKeyPath:@"tabImageViewDefaultOffset"]; } } #pragma mark - #pragma mark - public Methods - (instancetype)initWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes { return [self initWithViewControllers:viewControllers tabBarItemsAttributes:tabBarItemsAttributes imageInsets:UIEdgeInsetsZero titlePositionAdjustment:UIOffsetZero context:nil]; } - (instancetype)initWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes imageInsets:(UIEdgeInsets)imageInsets titlePositionAdjustment:(UIOffset)titlePositionAdjustment { return [self initWithViewControllers:viewControllers tabBarItemsAttributes:tabBarItemsAttributes imageInsets:imageInsets titlePositionAdjustment:titlePositionAdjustment context:nil]; } - (instancetype)initWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes imageInsets:(UIEdgeInsets)imageInsets titlePositionAdjustment:(UIOffset)titlePositionAdjustment context:(NSString *)context { if (self = [super init]) { _imageInsets = imageInsets; _titlePositionAdjustment = titlePositionAdjustment; _tabBarItemsAttributes = tabBarItemsAttributes; self.context = context; self.viewControllers = viewControllers; if ([self hasPlusChildViewController]) { self.delegate = self; } } return self; } - (void)setContext:(NSString *)context { if (context && context.length > 0) { _context = [context copy]; } else { _context = NSStringFromClass([CYLTabBarController class]); } [self.tabBar setValue:_context forKey:@"context"]; } + (instancetype)tabBarControllerWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes imageInsets:(UIEdgeInsets)imageInsets titlePositionAdjustment:(UIOffset)titlePositionAdjustment context:(NSString *)context { return [[self alloc] initWithViewControllers:viewControllers tabBarItemsAttributes:tabBarItemsAttributes imageInsets:imageInsets titlePositionAdjustment:titlePositionAdjustment context:context]; } + (instancetype)tabBarControllerWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes imageInsets:(UIEdgeInsets)imageInsets titlePositionAdjustment:(UIOffset)titlePositionAdjustment { return [[self alloc] initWithViewControllers:viewControllers tabBarItemsAttributes:tabBarItemsAttributes imageInsets:imageInsets titlePositionAdjustment:titlePositionAdjustment context:nil]; } + (instancetype)tabBarControllerWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes { return [self tabBarControllerWithViewControllers:viewControllers tabBarItemsAttributes:tabBarItemsAttributes imageInsets:UIEdgeInsetsZero titlePositionAdjustment:UIOffsetZero]; } - (void)hideTabBadgeBackgroundSeparator { [self.tabBar layoutIfNeeded]; self.tabBar.cyl_tabBadgeBackgroundSeparator.alpha = 0; self.tabBar.barStyle = UIBarStyleBlack; } + (BOOL)havePlusButton { if (CYLExternPlusButton) { return YES; } return NO; } + (NSUInteger)allItemsInTabBarCount { NSUInteger allItemsInTabBar = CYLTabbarItemsCount; if ([CYLTabBarController havePlusButton]) { allItemsInTabBar += 1; } return allItemsInTabBar; } - (id)appDelegate { return [UIApplication sharedApplication].delegate; } - (UIWindow *)rootWindow { UIWindow *result = nil; do { if ([self.appDelegate respondsToSelector:@selector(window)]) { result = [self.appDelegate window]; } if (result) { break; } } while (NO); return result; } #pragma mark - #pragma mark - Private Methods /** * 利用 KVC 把系统的 tabBar 类型改为自定义类型。 */ - (void)setUpTabBar { CYLTabBar *tabBar = [[CYLTabBar alloc] init]; [self setValue:tabBar forKey:@"tabBar"]; [tabBar cyl_setTabBarController:self]; } - (BOOL)hasPlusChildViewController { NSString *context = CYLPlusChildViewController.cyl_context; BOOL isSameContext = [context isEqualToString:self.context] && (context && self.context); // || (!context && !self.context); BOOL hasPlusChildViewController = CYLPlusChildViewController && isSameContext;//&& !isAdded; return hasPlusChildViewController; } - (BOOL)isPlusViewControllerAdded:(NSArray *)viewControllers { if ([_viewControllers containsObject:CYLPlusChildViewController]) { return YES; } __block BOOL isAdded = NO; [_viewControllers enumerateObjectsUsingBlock:^(UIViewController * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([self isEqualViewController:obj compairedViewController:CYLPlusChildViewController]) { isAdded = YES; *stop = YES; return; } }]; return isAdded;; } - (void)setViewControllers:(NSArray *)viewControllers { if (_viewControllers && _viewControllers.count) { for (UIViewController *viewController in _viewControllers) { [viewController willMoveToParentViewController:nil]; [viewController.view removeFromSuperview]; [viewController removeFromParentViewController]; } BOOL isAdded = [self isPlusViewControllerAdded:_viewControllers]; BOOL hasPlusChildViewController = [self hasPlusChildViewController] && !isAdded; if (hasPlusChildViewController) { [CYLPlusChildViewController willMoveToParentViewController:nil]; [CYLPlusChildViewController.view removeFromSuperview]; [CYLPlusChildViewController removeFromParentViewController]; } } if (viewControllers && [viewControllers isKindOfClass:[NSArray class]]) { if ((!_tabBarItemsAttributes) || (_tabBarItemsAttributes.count != viewControllers.count)) { [NSException raise:NSStringFromClass([CYLTabBarController class]) format:@"The count of CYLTabBarControllers is not equal to the count of tabBarItemsAttributes.【Chinese】设置_tabBarItemsAttributes属性时,请确保元素个数与控制器的个数相同,并在方法`-setViewControllers:`之前设置"]; } //TODO: BOOL isAdded = [self isPlusViewControllerAdded:_viewControllers]; BOOL addedFlag = [CYLPlusChildViewController cyl_plusViewControllerEverAdded]; BOOL hasPlusChildViewController = [self hasPlusChildViewController] && !isAdded && !addedFlag; if (hasPlusChildViewController) { NSMutableArray *viewControllersWithPlusButton = [NSMutableArray arrayWithArray:viewControllers]; [viewControllersWithPlusButton insertObject:CYLPlusChildViewController atIndex:CYLPlusButtonIndex]; _viewControllers = [viewControllersWithPlusButton copy]; [CYLPlusChildViewController cyl_setPlusViewControllerEverAdded:YES]; } else { _viewControllers = [viewControllers copy]; } CYLTabbarItemsCount = [viewControllers count]; CYLTabBarItemWidth = ([UIScreen mainScreen].bounds.size.width - CYLPlusButtonWidth) / (CYLTabbarItemsCount); NSUInteger idx = 0; for (UIViewController *viewController in _viewControllers) { NSString *title = nil; id normalImageInfo = nil; id selectedImageInfo = nil; UIOffset titlePositionAdjustment = UIOffsetZero; UIEdgeInsets imageInsets = UIEdgeInsetsZero; if (viewController != CYLPlusChildViewController) { title = _tabBarItemsAttributes[idx][CYLTabBarItemTitle]; normalImageInfo = _tabBarItemsAttributes[idx][CYLTabBarItemImage]; selectedImageInfo = _tabBarItemsAttributes[idx][CYLTabBarItemSelectedImage]; NSValue *offsetValue = _tabBarItemsAttributes[idx][CYLTabBarItemTitlePositionAdjustment]; UIOffset offset = [offsetValue UIOffsetValue]; titlePositionAdjustment = offset; NSValue *insetsValue = _tabBarItemsAttributes[idx][CYLTabBarItemImageInsets]; UIEdgeInsets insets = [insetsValue UIEdgeInsetsValue]; imageInsets = insets; } else { idx--; } [self addOneChildViewController:viewController WithTitle:title normalImageInfo:normalImageInfo selectedImageInfo:selectedImageInfo titlePositionAdjustment:titlePositionAdjustment imageInsets:imageInsets ]; [[viewController cyl_getViewControllerInsteadOfNavigationController] cyl_setTabBarController:self]; idx++; } } else { for (UIViewController *viewController in _viewControllers) { [[viewController cyl_getViewControllerInsteadOfNavigationController] cyl_setTabBarController:nil]; } _viewControllers = nil; } } - (void)setTintColor:(UIColor *)tintColor { if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.f) { [self.tabBar setSelectedImageTintColor:tintColor]; } self.tabBar.tintColor = tintColor; } /** * 添加一个子控制器 * * @param viewController 控制器 * @param title 标题 * @param normalImageInfo 图片 * @param selectedImageInfo 选中图片 */ - (void)addOneChildViewController:(UIViewController *)viewController WithTitle:(NSString *)title normalImageInfo:(id)normalImageInfo selectedImageInfo:(id)selectedImageInfo titlePositionAdjustment:(UIOffset)titlePositionAdjustment imageInsets:(UIEdgeInsets)imageInsets { viewController.tabBarItem.title = title; if (normalImageInfo) { UIImage *normalImage = [self getImageFromImageInfo:normalImageInfo]; viewController.tabBarItem.image = normalImage; } if (selectedImageInfo) { UIImage *selectedImage = [self getImageFromImageInfo:selectedImageInfo]; viewController.tabBarItem.selectedImage = selectedImage; } if (self.shouldCustomizeImageInsets || ([self isNOTEmptyForImageInsets:imageInsets])) { UIEdgeInsets insets = (([self isNOTEmptyForImageInsets:imageInsets]) ? imageInsets : self.imageInsets); viewController.tabBarItem.imageInsets = insets; } if (self.shouldCustomizeTitlePositionAdjustment || [self isNOTEmptyForTitlePositionAdjustment:titlePositionAdjustment]) { UIOffset offset = (([self isNOTEmptyForTitlePositionAdjustment:titlePositionAdjustment]) ? titlePositionAdjustment : self.titlePositionAdjustment); viewController.tabBarItem.titlePositionAdjustment = offset; } [self addChildViewController:viewController]; } - (UIImage *)getImageFromImageInfo:(id)imageInfo { UIImage *image = nil; if ([imageInfo isKindOfClass:[NSString class]]) { image = [UIImage imageNamed:imageInfo]; image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; } else if ([imageInfo isKindOfClass:[UIImage class]]) { image = (UIImage *)imageInfo; } return image; } - (BOOL)shouldCustomizeImageInsets { BOOL shouldCustomizeImageInsets = [self isNOTEmptyForImageInsets:self.imageInsets]; return shouldCustomizeImageInsets; } - (BOOL)shouldCustomizeTitlePositionAdjustment { BOOL shouldCustomizeTitlePositionAdjustment = [self isNOTEmptyForTitlePositionAdjustment:self.titlePositionAdjustment]; return shouldCustomizeTitlePositionAdjustment; } - (BOOL)isNOTEmptyForImageInsets:(UIEdgeInsets)imageInsets { if (imageInsets.top != 0 || imageInsets.left != 0 || imageInsets.bottom != 0 || imageInsets.right != 0) { return YES; } return NO; } - (BOOL)isNOTEmptyForTitlePositionAdjustment:(UIOffset)titlePositionAdjustment { if (titlePositionAdjustment.horizontal != 0 || titlePositionAdjustment.vertical != 0) { return YES; } return NO; } #pragma mark - #pragma mark - KVO Method // KVO监听执行 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if(context != CYLTabImageViewDefaultOffsetContext) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } if(context == CYLTabImageViewDefaultOffsetContext) { CGFloat tabImageViewDefaultOffset = [change[NSKeyValueChangeNewKey] floatValue]; [self offsetTabBarTabImageViewToFit:tabImageViewDefaultOffset]; } } - (void)offsetTabBarTabImageViewToFit:(CGFloat)tabImageViewDefaultOffset { if (self.shouldCustomizeImageInsets) { return; } NSArray *tabBarItems = self.tabBar.items; [tabBarItems enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { UIEdgeInsets imageInset = UIEdgeInsetsMake(tabImageViewDefaultOffset, 0, -tabImageViewDefaultOffset, 0); obj.imageInsets = imageInset; if (!self.shouldCustomizeTitlePositionAdjustment) { obj.titlePositionAdjustment = UIOffsetMake(0, MAXFLOAT); } }]; } #pragma mark - delegate - (void)updateSelectionStatusIfNeededForTabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController { UIButton *plusButton = CYLExternPlusButton; CYLTabBarController *tabBarViewController = [[CYLPlusChildViewController cyl_getViewControllerInsteadOfNavigationController] cyl_tabBarController]; NSArray *viewControllers = tabBarViewController.viewControllers; BOOL isAdded = [self isPlusViewControllerAdded:viewControllers]; BOOL hasPlusChildViewController = isAdded; BOOL isNotCurrentViewController = ![self isEqualViewController:viewController compairedViewController:CYLPlusChildViewController]; BOOL shouldConfigureSelectionStatus = (hasPlusChildViewController && isNotCurrentViewController); if (shouldConfigureSelectionStatus) { plusButton.selected = NO; } } - (BOOL)isEqualViewController:(UIViewController *)viewController compairedViewController:(UIViewController *)compairedViewController { if ([viewController isEqual:compairedViewController]) { return YES; } if ([[viewController cyl_getViewControllerInsteadOfNavigationController] isEqual:[compairedViewController cyl_getViewControllerInsteadOfNavigationController]]) { return YES; } return NO; } - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController { [self updateSelectionStatusIfNeededForTabBarController:tabBarController shouldSelectViewController:viewController]; return YES; } - (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control { } - (void)didSelectControl:(UIControl *)control { SEL actin = @selector(tabBarController:didSelectControl:); if ([self.delegate respondsToSelector:actin]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [self.delegate performSelector:actin withObject:self withObject:control]; #pragma clang diagnostic pop } } - (id)rootViewController { CYLTabBarController *tabBarController = nil; id delegate = ((id)[[UIApplication sharedApplication] delegate]); UIWindow *window = delegate.window; UIViewController *rootViewController = [window.rootViewController cyl_getViewControllerInsteadOfNavigationController];; if ([rootViewController isKindOfClass:[CYLTabBarController class]]) { tabBarController = (CYLTabBarController *)rootViewController; } return tabBarController; } @end @implementation NSObject (CYLTabBarControllerReferenceExtension) - (void)cyl_setTabBarController:(CYLTabBarController *)tabBarController { //OBJC_ASSOCIATION_ASSIGN instead of OBJC_ASSOCIATION_RETAIN_NONATOMIC to avoid retain circle id __weak weakObject = tabBarController; id (^block)() = ^{ return weakObject; }; objc_setAssociatedObject(self, @selector(cyl_tabBarController), block, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (CYLTabBarController *)cyl_tabBarController { CYLTabBarController *tabBarController; id (^block)() = objc_getAssociatedObject(self, @selector(cyl_tabBarController)); tabBarController = (block ? block() : nil); if (tabBarController && [tabBarController isKindOfClass:[CYLTabBarController class]]) { return tabBarController; } if ([self isKindOfClass:[UIViewController class]] && [(UIViewController *)self parentViewController]) { tabBarController = [[(UIViewController *)self parentViewController] cyl_tabBarController]; if ([tabBarController isKindOfClass:[CYLTabBarController class]]) { return tabBarController; } } id delegate = ((id)[[UIApplication sharedApplication] delegate]); UIWindow *window = delegate.window; UIViewController *rootViewController = [window.rootViewController cyl_getViewControllerInsteadOfNavigationController];; if ([rootViewController isKindOfClass:[CYLTabBarController class]]) { tabBarController = (CYLTabBarController *)rootViewController; } return tabBarController; } @end