以下笔记内容仅供个人参考,如有理解错误,请高抬贵手,仙人指路,互相学习进步...
使用方法教程
使用方法及教程,查看项目源码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.总结
解读CYLTabBarController类文件,在源码中中文注释自己的理解👇
CYLTabBarController类文件(.h,.m)
解读CYLTabBarController.h,CYLTabBarController.m
可能需要理解的知识点:
.生命周期分析 【 http://www.jianshu.com/p/178f2ba8b5e6】
.FOUNDATION_EXTERN 【 http://www.jianshu.com/p/8f9d8a5c9e3b】
.Block代码块 【 http://www.jianshu.com/p/14efa33b3562】
.类别Category扩展 【http://www.jianshu.com/p/b49e02eb7eb3】
.runtime消息机制 【 https://github.com/Tuccuay/RuntimeSummary】
.KVC设计模式 【 http://www.jianshu.com/p/45cbd324ea65】
.KVO设计模式 【 http://www.jianshu.com/p/e59bb8f59302】
.视图添加子视图 【http://www.jianshu.com/p/ff8579d40079】
CYLTabBarController.h文件
#import "CYLPlusButton.h"
#import "UIViewController+CYLTabBarControllerExtention.h"
#import "UIView+CYLTabBarControllerExtention.h"
#import "UITabBarItem+CYLTabBarControllerExtention.h"
#import "UIControl+CYLTabBarControllerExtention.h"
@class CYLTabBarController;
//定义了一个代码块类型:CYLViewDidLayoutSubViewsBlock
typedef void(^CYLViewDidLayoutSubViewsBlock)(CYLTabBarController *tabBarController);
//定义了全局变量,实现全局调用
FOUNDATION_EXTERN NSString *const CYLTabBarItemTitle;
FOUNDATION_EXTERN NSString *const CYLTabBarItemImage;
FOUNDATION_EXTERN NSString *const CYLTabBarItemSelectedImage;
FOUNDATION_EXTERN NSUInteger CYLTabbarItemsCount;
FOUNDATION_EXTERN NSUInteger CYLPlusButtonIndex;
FOUNDATION_EXTERN CGFloat CYLPlusButtonWidth;
FOUNDATION_EXTERN CGFloat CYLTabBarItemWidth;
// 创建了一个代理:CYLTabBarControllerDelegate
@protocol CYLTabBarControllerDelegate <NSObject>
- (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control;
@end
//自定义了一个类:CYLTabBarController,继承系统类:UITabBarController,并且遵循上面创建的代理
@interface CYLTabBarController : UITabBarController <CYLTabBarControllerDelegate>
//定义了一个block代码块属性
@property (nonatomic, copy) CYLViewDidLayoutSubViewsBlock viewDidLayoutSubviewsBlock;
//提供设置block代码块属性的方法
- (void)setViewDidLayoutSubViewsBlock:(CYLViewDidLayoutSubViewsBlock)viewDidLayoutSubviewsBlock;
//装载tabbar的控制器数组
@property (nonatomic, readwrite, copy) NSArray<UIViewController *> *viewControllers;
//装载tabbar items属性的字典数组
@property (nonatomic, readwrite, copy) NSArray<NSDictionary *> *tabBarItemsAttributes;
//自定义tabbar控件的高度
@property (nonatomic, assign) CGFloat tabBarHeight;
//读取tabbarItem的图片的偏移量,默认是UIEdgeInsetsZero
@property (nonatomic, readonly, assign) UIEdgeInsets imageInsets;
//读取UIBarItem label text的属性偏移
@property (nonatomic, readonly, assign) UIOffset titlePositionAdjustment;
//初始化方法(类方法,对象方法)
- (instancetype)initWithViewControllers:(NSArray<UIViewController *> *)viewControllers
tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes;
+ (instancetype)tabBarControllerWithViewControllers:(NSArray<UIViewController *> *)viewControllers
tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes;
- (instancetype)initWithViewControllers:(NSArray<UIViewController *> *)viewControllers
tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes
imageInsets:(UIEdgeInsets)imageInsets
titlePositionAdjustment:(UIOffset)titlePositionAdjustment;
+ (instancetype)tabBarControllerWithViewControllers:(NSArray<UIViewController *> *)viewControllers
tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes
imageInsets:(UIEdgeInsets)imageInsets
titlePositionAdjustment:(UIOffset)titlePositionAdjustment;
- (void)updateSelectionStatusIfNeededForTabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController;
//判断tabbar是否有一个自定义的凸起按钮
+ (BOOL)havePlusButton;
//所有的item个数,注意(包括那个自定义的凸起按钮)
+ (NSUInteger)allItemsInTabBarCount;
//获取APPdelegate对象
- (id<UIApplicationDelegate>)appDelegate;
//获取跟视图窗口
- (UIWindow *)rootWindow;
@end
/**
* @bref 添加了一个NSObject的分类类别,实现所有对象都可以获取到tabbar控制器对象
*/
@interface NSObject (CYLTabBarControllerReferenceExtension)
//通过runtime机制,添加一个分类属性cyl_tabBarController,并且改变默认的setter方法
//改变setter的默认方法,为了统一以cyl_为前缀的风格
@property (nonatomic, setter=cyl_setTabBarController:) CYLTabBarController *cyl_tabBarController;
@end
//定义了一个tabbar 元素item宽度改变的通知
FOUNDATION_EXTERN NSString *const CYLTabBarItemWidthDidChangeNotification;
CYLTabBarController.m文件
#import "CYLTabBarController.h"
#import "CYLTabBar.h"
#import <objc/runtime.h>
#import "UIViewController+CYLTabBarControllerExtention.h"
//初始化.h文件定义的全局变量
NSString *const CYLTabBarItemTitle = @"CYLTabBarItemTitle";
NSString *const CYLTabBarItemImage = @"CYLTabBarItemImage";
NSString *const CYLTabBarItemSelectedImage = @"CYLTabBarItemSelectedImage";
NSUInteger CYLTabbarItemsCount = 0;
NSUInteger CYLPlusButtonIndex = 0;
CGFloat CYLTabBarItemWidth = 0.0f;
NSString *const CYLTabBarItemWidthDidChangeNotification = @"CYLTabBarItemWidthDidChangeNotification";
//定义了一个静态的标志符号常量,用于下面代码的KVO注册监听context的标识绑定
static void * const CYLTabImageViewDefaultOffsetContext = (void*)&CYLTabImageViewDefaultOffsetContext;
//扩展子类CYLTabBarController的属性
@interface CYLTabBarController () <UITabBarControllerDelegate>
@property (nonatomic, assign, getter=isObservingTabImageViewDefaultOffset) BOOL observingTabImageViewDefaultOffset;
@end
//CYLTabBarController类的实现过程
@implementation CYLTabBarController
//指定属性的实例为_viewControllers,没有明显左右,
@synthesize viewControllers = _viewControllers;
#pragma mark - 生命周期函数
- (void)viewDidLoad {
[super viewDidLoad];
// KVC,修改系统的Tabbar,把系统的tabbar替换成自定义的Tabbar控件,处理tabBar,使用自定义 tabBar 添加 发布按钮
[self setUpTabBar];
// KVO注册监听
if (!self.isObservingTabImageViewDefaultOffset) {
//tabImageViewDefaultOffset这个属性是自定义CYLTabbar中的自定义属性
//CYLTabImageViewDefaultOffsetContext头部定义的静态常量标志
[self.tabBar addObserver:self forKeyPath:@"tabImageViewDefaultOffset" options:NSKeyValueObservingOptionNew context:CYLTabImageViewDefaultOffsetContext];
self.observingTabImageViewDefaultOffset = YES;
}
}
//Fix issue #93
- (void)viewDidLayoutSubviews {
[self.tabBar layoutSubviews];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UITabBar *tabBar = self.tabBar;
//遍历tabbar控件的子视图,给每个只视图添加didSelectControl:方法,这个方法主要是传递control对象,假如代理delegate响应tabBarController:didSelectControl:方法,就执行tabBarController:didSelectControl:方法
for (UIControl *control in tabBar.subviews) {
if ([control isKindOfClass:[UIControl class]]) {
SEL actin = @selector(didSelectControl:);
[control addTarget:self action:actin forControlEvents:UIControlEventTouchUpInside];
}
}
//假如存在viewDidLayoutSubviewsBlock变量,传递self出去
!self.viewDidLayoutSubviewsBlock ?: self.viewDidLayoutSubviewsBlock(self);
});
}
- (void)viewWillLayoutSubviews {
if (CYL_IS_IOS_11 || !self.tabBarHeight) {
return;
}
//假如不是iOS11系统,重新设置tabbar的frame值
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;
}
}
//销毁方法,移除kvo监听
- (void)dealloc {
// KVO反注册
if (self.isObservingTabImageViewDefaultOffset) {
[self.tabBar removeObserver:self forKeyPath:@"tabImageViewDefaultOffset"];
}
}
//赋值代码块
- (void)setViewDidLayoutSubViewsBlock:(CYLViewDidLayoutSubViewsBlock)viewDidLayoutSubviewsBlock {
_viewDidLayoutSubviewsBlock = viewDidLayoutSubviewsBlock;
}
#pragma mark - 初始化方法
//创建实例对象方法
- (instancetype)initWithViewControllers:(NSArray<UIViewController *> *)viewControllers tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes {
return [self initWithViewControllers:viewControllers
tabBarItemsAttributes:tabBarItemsAttributes
imageInsets:UIEdgeInsetsZero
titlePositionAdjustment:UIOffsetZero];
}
- (instancetype)initWithViewControllers:(NSArray<UIViewController *> *)viewControllers
tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes
imageInsets:(UIEdgeInsets)imageInsets
titlePositionAdjustment:(UIOffset)titlePositionAdjustment {
if (self = [super init]) {
_imageInsets = imageInsets;
_titlePositionAdjustment = titlePositionAdjustment;
_tabBarItemsAttributes = tabBarItemsAttributes;
self.viewControllers = viewControllers;
if (CYLPlusChildViewController) {
self.delegate = self;
}
}
return self;
}
//创建实例类方法
+ (instancetype)tabBarControllerWithViewControllers:(NSArray<UIViewController *> *)viewControllers
tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes
imageInsets:(UIEdgeInsets)imageInsets
titlePositionAdjustment:(UIOffset)titlePositionAdjustment {
return [[self alloc] initWithViewControllers:viewControllers
tabBarItemsAttributes:tabBarItemsAttributes
imageInsets:imageInsets
titlePositionAdjustment:titlePositionAdjustment];
}
+ (instancetype)tabBarControllerWithViewControllers:(NSArray<UIViewController *> *)viewControllers tabBarItemsAttributes:(NSArray<NSDictionary *> *)tabBarItemsAttributes {
return [self tabBarControllerWithViewControllers:viewControllers
tabBarItemsAttributes:tabBarItemsAttributes
imageInsets:UIEdgeInsetsZero
titlePositionAdjustment:UIOffsetZero];
}
+ (BOOL)havePlusButton {
//假如CYLExternPlusButton不为空,表示存在自定义凸出按钮
if (CYLExternPlusButton) {
return YES;
}
return NO;
}
//返回tabbar item元素的个数
+ (NSUInteger)allItemsInTabBarCount {
NSUInteger allItemsInTabBar = CYLTabbarItemsCount;
if ([CYLTabBarController havePlusButton]) {
allItemsInTabBar += 1;
}
return allItemsInTabBar;
}
//返回APPDelegate代理
- (id<UIApplicationDelegate>)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;
}
/**
* 利用 KVC 把系统的 tabBar 类型改为自定义类型。
*/
- (void)setUpTabBar {
[self setValue:[[CYLTabBar alloc] init] forKey:@"tabBar"];
}
- (void)setViewControllers:(NSArray *)viewControllers {
if (_viewControllers && _viewControllers.count) {//遍历视图,并且清空之前的视图,从父视图中删除
for (UIViewController *viewController in _viewControllers) {
[viewController willMoveToParentViewController:nil];
[viewController.view removeFromSuperview];
[viewController removeFromParentViewController];
}
}
if (viewControllers && [viewControllers isKindOfClass:[NSArray class]]) {
if ((!_tabBarItemsAttributes) || (_tabBarItemsAttributes.count != viewControllers.count)) {
[NSException raise:@"CYLTabBarController" format:@"设置_tabBarItemsAttributes属性时,请确保元素个数与控制器的个数相同,并在方法`-setViewControllers:`之前设置"];
}
//假如凸出按钮控制器存在的话,插入到控制器数组_viewControllers中,不存在,就拷贝控制器进行属性赋值操作
if (CYLPlusChildViewController) {
NSMutableArray *viewControllersWithPlusButton = [NSMutableArray arrayWithArray:viewControllers];
[viewControllersWithPlusButton insertObject:CYLPlusChildViewController atIndex:CYLPlusButtonIndex];
_viewControllers = [viewControllersWithPlusButton copy];
} 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;
if (viewController != CYLPlusChildViewController) {
title = _tabBarItemsAttributes[idx][CYLTabBarItemTitle];
normalImageInfo = _tabBarItemsAttributes[idx][CYLTabBarItemImage];
selectedImageInfo = _tabBarItemsAttributes[idx][CYLTabBarItemSelectedImage];
} else {
//因为配置信息的字典数组不包括自定义按钮的,所以需要回退一个位置
idx--;
}
//根据前面获取到的信息进行添加子视图控制器
[self addOneChildViewController:viewController
WithTitle:title
normalImageInfo:normalImageInfo
selectedImageInfo:selectedImageInfo];
//runtime添加Tabbar控制器
[viewController cyl_setTabBarController:self];
idx++;
}
} else {
//清空属性数组,设置tabbarController为nil
for (UIViewController *viewController in _viewControllers) {
[viewController cyl_setTabBarController:nil];
}
_viewControllers = nil;
}
}
/**
* 添加一个子控制器
*
* @param viewController 控制器
* @param title 标题
* @param normalImageInfo 图片
* @param selectedImageInfo 选中图片
*/
- (void)addOneChildViewController:(UIViewController *)viewController
WithTitle:(NSString *)title
normalImageInfo:(id)normalImageInfo
selectedImageInfo:(id)selectedImageInfo {
viewController.tabBarItem.title = title;
if (normalImageInfo) {
UIImage *normalImage = [self getImageFromImageInfo:normalImageInfo];
normalImage = [normalImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
viewController.tabBarItem.image = normalImage;
}
if (selectedImageInfo) {
UIImage *selectedImage = [self getImageFromImageInfo:selectedImageInfo];
selectedImage = [selectedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
viewController.tabBarItem.selectedImage = selectedImage;
}
if (self.shouldCustomizeImageInsets) {
viewController.tabBarItem.imageInsets = self.imageInsets;
}
if (self.shouldCustomizeTitlePositionAdjustment) {
viewController.tabBarItem.titlePositionAdjustment = self.titlePositionAdjustment;
}
[self addChildViewController:viewController];
}
//获取图片
- (UIImage *)getImageFromImageInfo:(id)imageInfo {
UIImage *image = nil;
if ([imageInfo isKindOfClass:[NSString class]]) {
image = [UIImage imageNamed:imageInfo];
} else if ([imageInfo isKindOfClass:[UIImage class]]) {
image = (UIImage *)imageInfo;
}
return image;
}
//判断是否需要自定义图片偏移量
- (BOOL)shouldCustomizeImageInsets {
BOOL shouldCustomizeImageInsets = self.imageInsets.top != 0.f || self.imageInsets.left != 0.f || self.imageInsets.bottom != 0.f || self.imageInsets.right != 0.f;
return shouldCustomizeImageInsets;
}
//判断是否需要自定义文本偏移量
- (BOOL)shouldCustomizeTitlePositionAdjustment {
BOOL shouldCustomizeTitlePositionAdjustment = self.titlePositionAdjustment.horizontal != 0.f || self.titlePositionAdjustment.vertical != 0.f;
return shouldCustomizeTitlePositionAdjustment;
}
#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;
}
//遍历tabbar item元素,枚举遍历数组,设置图片的偏移量
NSArray<UITabBarItem *> *tabBarItems = [self cyl_tabBarController].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
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
[[self cyl_tabBarController] 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
}
}
- (void)updateSelectionStatusIfNeededForTabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController {
NSUInteger selectedIndex = tabBarController.selectedIndex;
UIButton *plusButton = CYLExternPlusButton;
BOOL shouldConfigureSelectionStatus = CYLPlusChildViewController && ((selectedIndex == CYLPlusButtonIndex) && (viewController != CYLPlusChildViewController));
if (shouldConfigureSelectionStatus) {
plusButton.selected = NO;
}
}
@end
@implementation NSObject (CYLTabBarControllerReferenceExtension)
//runtime关联属性,属性赋值,@selector(cyl_tabBarController)返回一个唯一的标识key
- (void)cyl_setTabBarController:(CYLTabBarController *)tabBarController {
objc_setAssociatedObject(self, @selector(cyl_tabBarController), tabBarController, OBJC_ASSOCIATION_ASSIGN);
}
//获取tabBarController控制器
- (CYLTabBarController *)cyl_tabBarController {
//根据@selector(cyl_tabBarController)返回一个唯一的标识key,获取key对应的属性对象
CYLTabBarController *tabBarController = objc_getAssociatedObject(self, @selector(cyl_tabBarController));
if (tabBarController) {
return tabBarController;
}
if ([self isKindOfClass:[UIViewController class]] && [(UIViewController *)self parentViewController]) {
tabBarController = [[(UIViewController *)self parentViewController] cyl_tabBarController];
return tabBarController;
}
id<UIApplicationDelegate> delegate = ((id<UIApplicationDelegate>)[[UIApplication sharedApplication] delegate]);
UIWindow *window = delegate.window;
//返回根视图
UIViewController *rootViewController = [window.rootViewController cyl_getViewControllerInsteadIOfNavigationController];
//返回tabBarController
if ([rootViewController isKindOfClass:[CYLTabBarController class]]) {
tabBarController = (CYLTabBarController *)window.rootViewController;
}
return tabBarController;
}
@end
UITabbarController总结:
1、自定义了一个UITabbarController的子类,主要实现定义一些全局变量,与tabbar相关的属性,以及初始化子类方法,另外添加了一个NSObject对象的分类扩展,定义了一个宽度变化通知。
2、通过外部赋值一个属性字典,通字典获取每个item的文本、图片,以及是否设置偏移,进行设置tabbar控件的元素item,并且添加子视图到tabbarcontroller控制器中,另外遍历tabbar控件子视图,假如响应代理,就给每个item执行tabbar点击control的代理方法