CYLTabBarController 第一篇(Tabbar底部栏)

以下笔记内容仅供个人参考,如有理解错误,请高抬贵手,仙人指路,互相学习进步...

使用方法教程

使用方法及教程,查看项目源码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 
- (void)tabBarController:(UITabBarController *)tabBarController didSelectControl:(UIControl *)control;
@end


//自定义了一个类:CYLTabBarController,继承系统类:UITabBarController,并且遵循上面创建的代理
@interface CYLTabBarController : UITabBarController 

//定义了一个block代码块属性
@property (nonatomic, copy) CYLViewDidLayoutSubViewsBlock viewDidLayoutSubviewsBlock;

//提供设置block代码块属性的方法
- (void)setViewDidLayoutSubViewsBlock:(CYLViewDidLayoutSubViewsBlock)viewDidLayoutSubviewsBlock;

//装载tabbar的控制器数组
@property (nonatomic, readwrite, copy) NSArray *viewControllers;

//装载tabbar items属性的字典数组
@property (nonatomic, readwrite, copy) NSArray *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 *)viewControllers
                  tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes;

+ (instancetype)tabBarControllerWithViewControllers:(NSArray *)viewControllers
                              tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes;

- (instancetype)initWithViewControllers:(NSArray *)viewControllers
                  tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes
                            imageInsets:(UIEdgeInsets)imageInsets
                titlePositionAdjustment:(UIOffset)titlePositionAdjustment;

+ (instancetype)tabBarControllerWithViewControllers:(NSArray *)viewControllers
                              tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes
                                        imageInsets:(UIEdgeInsets)imageInsets
                            titlePositionAdjustment:(UIOffset)titlePositionAdjustment;


- (void)updateSelectionStatusIfNeededForTabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController;

//判断tabbar是否有一个自定义的凸起按钮
+ (BOOL)havePlusButton;

//所有的item个数,注意(包括那个自定义的凸起按钮)
+ (NSUInteger)allItemsInTabBarCount;

//获取APPdelegate对象
- (id)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 
#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 () 
@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 *)viewControllers tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes {
    return [self initWithViewControllers:viewControllers
                   tabBarItemsAttributes:tabBarItemsAttributes
                             imageInsets:UIEdgeInsetsZero
                 titlePositionAdjustment:UIOffsetZero];
}

- (instancetype)initWithViewControllers:(NSArray *)viewControllers
                  tabBarItemsAttributes:(NSArray *)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 *)viewControllers
                              tabBarItemsAttributes:(NSArray *)tabBarItemsAttributes
                                        imageInsets:(UIEdgeInsets)imageInsets
                            titlePositionAdjustment:(UIOffset)titlePositionAdjustment {
    return [[self alloc] initWithViewControllers:viewControllers
                           tabBarItemsAttributes:tabBarItemsAttributes
                                     imageInsets:imageInsets
                         titlePositionAdjustment:titlePositionAdjustment];
}

+ (instancetype)tabBarControllerWithViewControllers:(NSArray *)viewControllers tabBarItemsAttributes:(NSArray *)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)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 *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 delegate = ((id)[[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的代理方法

你可能感兴趣的:(CYLTabBarController 第一篇(Tabbar底部栏))