//
|
// 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<CYLPlusButtonSubclassing> *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<CYLPlusButtonSubclassing> *)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<CYLPlusButtonSubclassing> *)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
|