以下笔记内容仅供个人参考,如有理解错误,请高抬贵手,仙人指路,互相学习进步...
使用方法教程
使用方法及教程,查看项目源码github地址:https://github.com/ChenYilong/CYLTabBarController,非常感谢开源的作者,开源促进社区的发展,共建和谐社会😆!
框架全部文件结构
1.CYLTabBarController
2. CYLTabBar
3.CYLPlusButton
4.UIViewController+CYLTabBarControllerExtention
5.UIView+CYLTabBarControllerExtention
6.UITabBarItem+CYLTabBarControllerExtention
7.UIControl+CYLTabBarControllerExtention
8.CYLConstants
8.总结
解读CYLTabBar类文件,在源码中中文注释自己的理解👇
CYLTabBar类文件(.h,.m)
解读CYLTabBar.h, CYLTabBar.m
可能需要理解的知识点:
. sizeThatFits 【 http://www.jianshu.com/p/c9ce5e195a07】
.Block代码块 【 http://www.jianshu.com/p/14efa33b3562】
.KVO设计模式 【 http://www.jianshu.com/p/e59bb8f59302】
.KVC设计模式 【 http://www.jianshu.com/p/45cbd324ea65】
CYLTabBarController.h文件
#import <UIKit/UIKit.h>
@interface CYLTabBar : UITabBar
/*!
* 让 `TabImageView` 垂直居中时,所需要的默认偏移量。
* `viewController.tabBarItem.imageInsets = UIEdgeInsetsMake(tabImageViewDefaultOffset, 0, -tabImageViewDefaultOffset, 0);`
*/
//所需要的默认偏移量,在CYLTabBarController第一篇的解读中,通过kvo监听这个属性进行位置调整设置
@property (nonatomic, assign, readonly) CGFloat tabImageViewDefaultOffset;
@end
CYLTabbar.m
#import "CYLTabBar.h"
#import "CYLPlusButton.h"
#import "CYLTabBarController.h"
#import "CYLConstants.h"
//定义一个静态常量,KVO注册监听的标识
static void *const CYLTabBarContext = (void*)&CYLTabBarContext;
@interface CYLTabBar ()
//遵循CYLPlusButtonSubclassing协议的一个凸出按钮
@property (nonatomic, strong) UIButton<CYLPlusButtonSubclassing> *plusButton;
//item的宽度
@property (nonatomic, assign) CGFloat tabBarItemWidth;
//item的数组
@property (nonatomic, copy) NSArray *tabBarButtonArray;
@end
@implementation CYLTabBar
#pragma mark -
#pragma mark - 初始化
- (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;
}
- (instancetype)sharedInit {
if (CYLExternPlusButton) {
self.plusButton = CYLExternPlusButton;
[self addSubview:(UIButton *)self.plusButton];
}
// KVO注册监听(上面自定义的属性tabBarItemWidth)
_tabBarItemWidth = CYLTabBarItemWidth;
[self addObserver:self forKeyPath:@"tabBarItemWidth" options:NSKeyValueObservingOptionNew context:CYLTabBarContext];
return self;
}
//适配iOS11
- (CGSize)sizeThatFits:(CGSize)size {
CGSize sizeThatFits = [super sizeThatFits:size];
CGFloat height = [self cyl_tabBarController].tabBarHeight;
if (height > 0 && !CYL_IS_IPHONE_X && CYL_IS_IOS_11) {
sizeThatFits.height = [self cyl_tabBarController].tabBarHeight;
}
return sizeThatFits;
}
//懒加载,获取元素数组
- (NSArray *)tabBarButtonArray {
if (_tabBarButtonArray == nil) {
_tabBarButtonArray = @[];
}
return _tabBarButtonArray;
}
//布局控件
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat taBarWidth = self.bounds.size.width;
CGFloat taBarHeight = self.bounds.size.height;
CYLTabBarItemWidth = (taBarWidth - CYLPlusButtonWidth) / CYLTabbarItemsCount;
self.tabBarItemWidth = CYLTabBarItemWidth;
//获取根据位置X的大小来排序子视图
NSArray *sortedSubviews = [self sortedSubviews];
//从排好序的tabbar子视图数组中,筛选出来UITabBarItem数组
self.tabBarButtonArray = [self tabBarButtonFromTabBarSubviews:sortedSubviews];
//遍历TabbarItem的子视图,设置获取偏移,tabImageViewDefaultOffset
[self setupTabImageViewDefaultOffset:self.tabBarButtonArray[0]];
if (!CYLExternPlusButton) {
return;
}
//假如有自定义的凸起按钮,设置凸起按钮的center
CGFloat multiplierOfTabBarHeight = [self multiplierOfTabBarHeight:taBarHeight];
CGFloat constantOfPlusButtonCenterYOffset = [self constantOfPlusButtonCenterYOffsetForTabBarHeight:taBarHeight];
self.plusButton.center = CGPointMake(taBarWidth * 0.5, taBarHeight * multiplierOfTabBarHeight + constantOfPlusButtonCenterYOffset);
NSUInteger plusButtonIndex = [self plusButtonIndex];
//根据凸起按钮的index位置进行遍历修改其他tabbarItem的位置
[self.tabBarButtonArray enumerateObjectsUsingBlock:^(UIView * _Nonnull childView, NSUInteger buttonIndex, BOOL * _Nonnull stop) {
//调整UITabBarItem的位置
CGFloat childViewX;
if (buttonIndex >= plusButtonIndex) {
childViewX = buttonIndex * CYLTabBarItemWidth + CYLPlusButtonWidth;
} else {
childViewX = buttonIndex * CYLTabBarItemWidth;
}
//仅修改childView的x和宽度,yh值不变
childView.frame = CGRectMake(childViewX,
CGRectGetMinY(childView.frame),
CYLTabBarItemWidth,
CGRectGetHeight(childView.frame)
);
}];
//把凸起按钮放到最上层视图
[self bringSubviewToFront:self.plusButton];
}
#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;
}
//发送通知,CYLTabBarItemWidthDidChangeNotification,在CYLTabBarController中定义了
if(context == CYLTabBarContext) {
[[NSNotificationCenter defaultCenter] postNotificationName:CYLTabBarItemWidthDidChangeNotification object:self];
}
}
- (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"];
}
}
//获取代理中taBarHeight的高度比例
- (CGFloat)multiplierOfTabBarHeight:(CGFloat)taBarHeight {
CGFloat multiplierOfTabBarHeight;
if ([[self.plusButton class] respondsToSelector:@selector(multiplierOfTabBarHeight:)]) {
multiplierOfTabBarHeight = [[self.plusButton class] multiplierOfTabBarHeight:taBarHeight];
}
#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)taBarHeight {
CGFloat constantOfPlusButtonCenterYOffset = 0.f;
if ([[self.plusButton class] respondsToSelector:@selector(constantOfPlusButtonCenterYOffsetForTabBarHeight:)]) {
constantOfPlusButtonCenterYOffset = [[self.plusButton class] constantOfPlusButtonCenterYOffsetForTabBarHeight:taBarHeight];
}
return constantOfPlusButtonCenterYOffset;
}
//获取凸起按钮的index位置
- (NSUInteger)plusButtonIndex {
NSUInteger plusButtonIndex;
if ([[self.plusButton class] respondsToSelector:@selector(indexOfPlusButtonInTabBar)]) {
//根据代理获取凸起按钮的index位置
plusButtonIndex = [[self.plusButton class] indexOfPlusButtonInTabBar];
//仅修改self.plusButton的x,ywh值不变
self.plusButton.frame = CGRectMake(plusButtonIndex * CYLTabBarItemWidth,
CGRectGetMinY(self.plusButton.frame),
CGRectGetWidth(self.plusButton.frame),
CGRectGetHeight(self.plusButton.frame)
);
} else {
if (CYLTabbarItemsCount % 2 != 0) {
[NSException raise:@"CYLTabBarController" 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;
}
//根据位置x排序视图得到视图数组
- (NSArray *)sortedSubviews {
NSArray *sortedSubviews = [self.subviews 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;
}
//从排好序的tabbar子视图数组中,筛选出来UITabBarItem数组
- (NSArray *)tabBarButtonFromTabBarSubviews:(NSArray *)tabBarSubviews {
NSMutableArray *tabBarButtonMutableArray = [NSMutableArray arrayWithCapacity:tabBarSubviews.count - 1];
[tabBarSubviews enumerateObjectsUsingBlock:^(UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//遍历排好序的子视图,假如是uicontrol对象,再判断是不是tabbar前缀的对象如UITabBarItem
if ([obj cyl_isTabButton]) { //假如是UITabBarItem对象类型,
[tabBarButtonMutableArray addObject:obj];
}
}];
//假如存在自定义视图(凸起按钮视图),需要删除对应位置的元素
if (CYLPlusChildViewController) {
[tabBarButtonMutableArray removeObjectAtIndex:CYLPlusButtonIndex];
}
return [tabBarButtonMutableArray copy];
}
//遍历TabbarItem的子视图,找到imageView视图,根据这个视图进行设置赋值图片的偏移属性tabImageViewDefaultOffset
- (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;
//假如是tabbarItem的ImageView
BOOL isTabImageView = [obj cyl_isTabImageView];
if (isTabImageView) {
tabImageViewDefaultOffset = (tabBarHeight - tabImageViewHeight) * 0.5 * 0.5;
}
if (isTabImageView && tabImageViewDefaultOffset == 0.f) {
shouldCustomizeImageView = NO;
}
}];
if (shouldCustomizeImageView && !CYL_IS_IPHONE_X) {
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 (CYLExternPlusButton) {
CGRect plusButtonFrame = self.plusButton.frame;
BOOL isInPlusButtonFrame = CGRectContainsPoint(plusButtonFrame, point);
if (isInPlusButtonFrame) {
return CYLExternPlusButton;
}
}
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
总结:
通过继承系统的UITabbar控件,自定义了CYLTabbar类,这个类是是通过KVC替换UITabbarController中的系统UITabbar,替换为CYLTabbar
主要功能:
1、根据遍历tabbar中的子视图,给每个Item对象的图片做偏移量设置处理,设置每个item的宽度,并且KVO进行监听item的宽度变化,发出通知
2、通过获取凸起按钮的代理设置的偏移常量,高度比例因子,凸起按钮的index位置,进行布局更新每个item的位置,并且设置处理好凸起按钮的位置。
3、通过对点击视图的方法进行改写,进行凸起按钮的点击事件处理,保证点击按钮凸出部分可以响应点击事件