// // CYLTabBar.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 "CYLTabBar.h" #import "CYLPlusButton.h" #import "CYLTabBarController.h" #import "CYLConstants.h" static void *const CYLTabBarContext = (void*)&CYLTabBarContext; @interface CYLTabBar () /** 发布按钮 */ @property (nonatomic, strong) UIButton *plusButton; @property (nonatomic, assign) CGFloat tabBarItemWidth; @property (nonatomic, copy) NSArray *tabBarButtonArray; @property (nonatomic, assign, getter=hasAddPlusButton) BOOL addPlusButton; @end @implementation CYLTabBar @synthesize plusButton = _plusButton; #pragma mark - #pragma mark - LifeCycle Method - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self = [self sharedInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { self = [self sharedInit]; } return self; } - (void)setPlusButton:(UIButton *)plusButton { if (!plusButton) { return; } _plusButton = plusButton; if (!self.hasAddPlusButton) { NSString *tabBarContext = self.tabBarContext; BOOL isFirstAdded = (_plusButton.superview == nil); BOOL isSameContext = [tabBarContext isEqualToString:self.context] && (tabBarContext && self.context); if (_plusButton && isSameContext && isFirstAdded) { [self addSubview:(UIButton *)_plusButton]; [_plusButton cyl_setTabBarController: [self cyl_tabBarController]]; } self.addPlusButton = YES; } } - (void)setContext:(NSString *)context { _context = context; self.plusButton = CYLExternPlusButton; } - (instancetype)sharedInit { // KVO注册监听 _tabBarItemWidth = CYLTabBarItemWidth; [self addObserver:self forKeyPath:@"tabBarItemWidth" options:NSKeyValueObservingOptionNew context:CYLTabBarContext]; return self; } - (CGSize)sizeThatFits:(CGSize)size { CGSize sizeThatFits = [super sizeThatFits:size]; CGFloat height = [self cyl_tabBarController].tabBarHeight; if (height > 0) { sizeThatFits.height = [self cyl_tabBarController].tabBarHeight; } return sizeThatFits; } /** * lazy load tabBarButtonArray * * @return NSArray */ - (NSArray *)tabBarButtonArray { if (_tabBarButtonArray == nil) { _tabBarButtonArray = @[]; } return _tabBarButtonArray; } - (NSString *)tabBarContext { NSString *tabBarContext; if ([[_plusButton class] respondsToSelector:@selector(tabBarContext)]) { tabBarContext = [[_plusButton class] tabBarContext]; } if (tabBarContext && tabBarContext.length > 0) { return tabBarContext; } tabBarContext = NSStringFromClass([CYLTabBarController class]); return tabBarContext; } - (UIButton *)plusButton { if (!CYLExternPlusButton || !_plusButton) { return nil; } NSString *tabBarContext = self.tabBarContext; BOOL addedToTabBar = [_plusButton.superview isEqual:self]; BOOL isSameContext = [tabBarContext isEqualToString:self.context] && (tabBarContext && self.context);;//|| (!tabBarContext && !self.context); if (_plusButton && addedToTabBar && isSameContext) { return _plusButton; } return nil; } - (void)layoutSubviews { [super layoutSubviews]; NSArray *sortedSubviews = [self sortedSubviews]; self.tabBarButtonArray = [self tabBarButtonFromTabBarSubviews:sortedSubviews]; if (self.tabBarButtonArray.count == 0) { return; } [self setupTabImageViewDefaultOffset:self.tabBarButtonArray[0]]; CGFloat tabBarWidth = self.bounds.size.width; CGFloat tabBarHeight = self.bounds.size.height; if (!CYLExternPlusButton) { return; } BOOL addedToTabBar = [_plusButton.superview isEqual:self]; if (!addedToTabBar) { CYLTabBarItemWidth = (tabBarWidth) / CYLTabbarItemsCount; [self.tabBarButtonArray enumerateObjectsUsingBlock:^(UIView * _Nonnull childView, NSUInteger buttonIndex, BOOL * _Nonnull stop) { //仅修改childView的x和宽度,yh值不变 CGFloat childViewX = buttonIndex * CYLTabBarItemWidth; [self changeXForChildView:childView childViewX:childViewX tabBarItemWidth:CYLTabBarItemWidth]; }]; } else { CYLTabBarItemWidth = (tabBarWidth - CYLPlusButtonWidth) / CYLTabbarItemsCount; CGFloat multiplierOfTabBarHeight = [self multiplierOfTabBarHeight:tabBarHeight]; CGFloat constantOfPlusButtonCenterYOffset = [self constantOfPlusButtonCenterYOffsetForTabBarHeight:tabBarHeight]; _plusButton.center = CGPointMake(tabBarWidth * 0.5, tabBarHeight * multiplierOfTabBarHeight + constantOfPlusButtonCenterYOffset); NSUInteger plusButtonIndex = [self plusButtonIndex]; [self.tabBarButtonArray enumerateObjectsUsingBlock:^(UIView * _Nonnull childView, NSUInteger buttonIndex, BOOL * _Nonnull stop) { //调整UITabBarItem的位置 CGFloat childViewX; if ([self hasPlusChildViewController]) { if (buttonIndex <= plusButtonIndex) { childViewX = buttonIndex * CYLTabBarItemWidth; } else { childViewX = (buttonIndex - 1) * CYLTabBarItemWidth + CYLPlusButtonWidth; } } else { if (buttonIndex >= plusButtonIndex) { childViewX = buttonIndex * CYLTabBarItemWidth + CYLPlusButtonWidth; } else { childViewX = buttonIndex * CYLTabBarItemWidth; } } //仅修改childView的x和宽度,yh值不变 [self changeXForChildView:childView childViewX:childViewX tabBarItemWidth:CYLTabBarItemWidth]; }]; //bring the plus button to top [self bringSubviewToFront:_plusButton]; } self.tabBarItemWidth = CYLTabBarItemWidth; } - (void)changeXForChildView:(UIView *)childView childViewX:(CGFloat)childViewX tabBarItemWidth:(CGFloat)tabBarItemWidth { //仅修改childView的x和宽度,yh值不变 childView.frame = CGRectMake(childViewX, CGRectGetMinY(childView.frame), tabBarItemWidth, CGRectGetHeight(childView.frame) ); } #pragma mark - #pragma mark - Private Methods + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { return NO; } // KVO监听执行 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if(context != CYLTabBarContext) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } if(context == CYLTabBarContext) { [[NSNotificationCenter defaultCenter] postNotificationName:CYLTabBarItemWidthDidChangeNotification object:self]; if (CYL_IS_IPHONE_X) { [self layoutIfNeeded]; } } } - (void)dealloc { // KVO反注册 [self removeObserver:self forKeyPath:@"tabBarItemWidth"]; } - (void)setTabBarItemWidth:(CGFloat )tabBarItemWidth { if (_tabBarItemWidth != tabBarItemWidth) { [self willChangeValueForKey:@"tabBarItemWidth"]; _tabBarItemWidth = tabBarItemWidth; [self didChangeValueForKey:@"tabBarItemWidth"]; } } - (void)setTabImageViewDefaultOffset:(CGFloat)tabImageViewDefaultOffset { if (tabImageViewDefaultOffset != 0.f) { [self willChangeValueForKey:@"tabImageViewDefaultOffset"]; _tabImageViewDefaultOffset = tabImageViewDefaultOffset; [self didChangeValueForKey:@"tabImageViewDefaultOffset"]; } } - (CGFloat)multiplierOfTabBarHeight:(CGFloat)tabBarHeight { CGFloat multiplierOfTabBarHeight; if ([[self.plusButton class] respondsToSelector:@selector(multiplierOfTabBarHeight:)]) { multiplierOfTabBarHeight = [[self.plusButton class] multiplierOfTabBarHeight:tabBarHeight]; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" else if ([[self.plusButton class] respondsToSelector:@selector(multiplerInCenterY)]) { multiplierOfTabBarHeight = [[self.plusButton class] multiplerInCenterY]; } #pragma clang diagnostic pop else { CGSize sizeOfPlusButton = self.plusButton.frame.size; CGFloat heightDifference = sizeOfPlusButton.height - self.bounds.size.height; if (heightDifference < 0) { multiplierOfTabBarHeight = 0.5; } else { CGPoint center = CGPointMake(self.bounds.size.height * 0.5, self.bounds.size.height * 0.5); center.y = center.y - heightDifference * 0.5; multiplierOfTabBarHeight = center.y / self.bounds.size.height; } } return multiplierOfTabBarHeight; } - (CGFloat)constantOfPlusButtonCenterYOffsetForTabBarHeight:(CGFloat)tabBarHeight { CGFloat constantOfPlusButtonCenterYOffset = 0.f; if ([[self.plusButton class] respondsToSelector:@selector(constantOfPlusButtonCenterYOffsetForTabBarHeight:)]) { constantOfPlusButtonCenterYOffset = [[self.plusButton class] constantOfPlusButtonCenterYOffsetForTabBarHeight:tabBarHeight]; } return constantOfPlusButtonCenterYOffset; } - (NSUInteger)plusButtonIndex { NSUInteger plusButtonIndex; if ([[self.plusButton class] respondsToSelector:@selector(indexOfPlusButtonInTabBar)]) { plusButtonIndex = [[self.plusButton class] indexOfPlusButtonInTabBar]; CGFloat childViewX = plusButtonIndex * CYLTabBarItemWidth; CGFloat tabBarItemWidth = CGRectGetWidth(self.plusButton.frame); [self changeXForChildView:self.plusButton childViewX:childViewX tabBarItemWidth:tabBarItemWidth]; } else { if (CYLTabbarItemsCount % 2 != 0) { [NSException raise:NSStringFromClass([CYLTabBarController class]) format:@"If the count of CYLTabbarControllers is odd,you must realizse `+indexOfPlusButtonInTabBar` in your custom plusButton class.【Chinese】如果CYLTabbarControllers的个数是奇数,你必须在你自定义的plusButton中实现`+indexOfPlusButtonInTabBar`,来指定plusButton的位置"]; } plusButtonIndex = CYLTabbarItemsCount * 0.5; } CYLPlusButtonIndex = plusButtonIndex; return plusButtonIndex; } /*! * Deal with some trickiness by Apple, You do not need to understand this method, somehow, it works. * NOTE: If the `self.title of ViewController` and `the correct title of tabBarItemsAttributes` are different, Apple will delete the correct tabBarItem from subViews, and then trigger `-layoutSubviews`, therefore subViews will be in disorder. So we need to rearrange them. */ - (NSArray *)sortedSubviews { if (self.subviews.count == 0) { return self.subviews; } NSMutableArray *tabBarButtonArray = [NSMutableArray arrayWithCapacity:self.subviews.count]; [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj cyl_isTabButton]) { [tabBarButtonArray addObject:obj]; } }]; NSArray *sortedSubviews = [[tabBarButtonArray copy] sortedArrayUsingComparator:^NSComparisonResult(UIView * formerView, UIView * latterView) { CGFloat formerViewX = formerView.frame.origin.x; CGFloat latterViewX = latterView.frame.origin.x; return (formerViewX > latterViewX) ? NSOrderedDescending : NSOrderedAscending; }]; return sortedSubviews; } - (NSArray *)tabBarButtonFromTabBarSubviews:(NSArray *)tabBarSubviews { if (tabBarSubviews.count == 0) { return tabBarSubviews; } NSMutableArray *tabBarButtonMutableArray = [NSMutableArray arrayWithCapacity:tabBarSubviews.count]; [tabBarSubviews enumerateObjectsUsingBlock:^(UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj cyl_isTabButton]) { [tabBarButtonMutableArray addObject:obj]; } }]; if ([self hasPlusChildViewController]) { @try { UIControl *control = tabBarButtonMutableArray[CYLPlusButtonIndex]; control.userInteractionEnabled = NO; control.hidden = YES; } @catch (NSException *exception) {} } return [tabBarButtonMutableArray copy]; } - (BOOL)hasPlusChildViewController { NSString *context = CYLPlusChildViewController.cyl_context; BOOL isSameContext = [context isEqualToString:self.context] && (context && self.context); BOOL isAdded = [[self cyl_tabBarController].viewControllers containsObject:CYLPlusChildViewController]; if (CYLPlusChildViewController && isSameContext && isAdded) { return YES; } return NO; } - (void)setupTabImageViewDefaultOffset:(UIView *)tabBarButton { __block BOOL shouldCustomizeImageView = YES; __block CGFloat tabImageViewHeight = 0.f; __block CGFloat tabImageViewDefaultOffset = 0.f; CGFloat tabBarHeight = self.frame.size.height; [tabBarButton.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj cyl_isTabLabel]) { shouldCustomizeImageView = NO; } tabImageViewHeight = obj.frame.size.height; BOOL isTabImageView = [obj cyl_isTabImageView]; if (isTabImageView) { tabImageViewDefaultOffset = (tabBarHeight - tabImageViewHeight) * 0.5 * 0.5; } if (isTabImageView && tabImageViewDefaultOffset == 0.f) { shouldCustomizeImageView = NO; } }]; if (shouldCustomizeImageView) { self.tabImageViewDefaultOffset = tabImageViewDefaultOffset; } } /*! * Capturing touches on a subview outside the frame of its superview. */ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { //1. 边界情况:不能响应点击事件 BOOL canNotResponseEvent = self.hidden || (self.alpha <= 0.01f) || (self.userInteractionEnabled == NO); if (canNotResponseEvent) { return nil; } //2. 优先处理 PlusButton (包括其突出的部分)、TabBarItems 未凸出的部分 //这一步主要是在处理只有两个 TabBarItems 的场景。 // 2.1先考虑clipsToBounds情况:子view超出部分没有显示出来 if (self.clipsToBounds && ![self pointInside:point withEvent:event]) { return nil; } if (self.plusButton) { CGRect plusButtonFrame = self.plusButton.frame; BOOL isInPlusButtonFrame = CGRectContainsPoint(plusButtonFrame, point); if (isInPlusButtonFrame) { return self.plusButton; } } NSArray *tabBarButtons = self.tabBarButtonArray; if (self.tabBarButtonArray.count == 0) { tabBarButtons = [self tabBarButtonFromTabBarSubviews:self.subviews]; } for (NSUInteger index = 0; index < tabBarButtons.count; index++) { UIView *selectedTabBarButton = tabBarButtons[index]; CGRect selectedTabBarButtonFrame = selectedTabBarButton.frame; BOOL isTabBarButtonFrame = CGRectContainsPoint(selectedTabBarButtonFrame, point); if (isTabBarButtonFrame) { return selectedTabBarButton; } } //3. 最后处理 TabBarItems 凸出的部分、添加到 TabBar 上的自定义视图、点击到 TabBar 上的空白区域 UIView *result = [super hitTest:point withEvent:event]; if (result) { return result; } for (UIView *subview in self.subviews.reverseObjectEnumerator) { CGPoint subPoint = [subview convertPoint:point fromView:self]; result = [subview hitTest:subPoint withEvent:event]; if (result) { return result; } } return nil; } @end