CYLTabBarController【低耦合集成TabBarController】
<p align="center">
bitHound
enter image description here
TabBarItem
仅显示图标,并自动使图标垂直居中,支持自定义TabBar高度支持横竖屏
enter image description here
本仓库配套Demo的效果: | 另一个Demo 使用CYLTabBarController实现了微博Tabbar框架,效果如下 |
---|---|
enter image description here | enter image description here |
项目结构
enter image description here做下说明:
├── CYLTabBarController #核心库文件夹,如果不使用 CocoaPods 集成,请直接将这个文件夹拖拽带你的项目中
└── Example
└── Classes
├── Module #模块类文件夹
│ ├── Home
│ ├── Message
│ ├── Mine
│ └── SameCity
└── View #这里放着 CYLPlusButton 的子类 CYLPlusButtonSubclass,演示了如何创建自定义的形状不规则加号按钮
使用CYLTabBarController
四步完成主流App框架搭建:
- 第一步:使用CocoaPods导入CYLTabBarController
- 第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组
- 第三步:将CYLTabBarController设置为window的RootViewController
- 第四步(可选):创建自定义的形状不规则加号按钮
第一步:使用CocoaPods导入CYLTabBarController
在 Podfile
中进行如下导入:
pod 'CYLTabBarController'
然后使用 cocoaPods
进行安装:
如果尚未安装 CocoaPods, 运行以下命令进行安装:
gem install cocoapods
安装成功后就可以安装依赖了:
建议使用如下方式:
# 禁止升级CocoaPods的spec仓库,否则会卡在 Analyzing dependencies ,非常慢
pod update --verbose --no-repo-update
如果提示找不到库,则可去掉 --no-repo-update
pod update
第二步:设置CYLTabBarController的两个数组:控制器数组和TabBar属性数组
- (void)setupViewControllers {
CYLHomeViewController *firstViewController = [[CYLHomeViewController alloc] init];
UIViewController *firstNavigationController = [[UINavigationController alloc]
initWithRootViewController:firstViewController];
CYLSameFityViewController *secondViewController = [[CYLSameFityViewController alloc] init];
UIViewController *secondNavigationController = [[UINavigationController alloc]
initWithRootViewController:secondViewController];
CYLTabBarController *tabBarController = [[CYLTabBarController alloc] init];
[self customizeTabBarForController:tabBarController];
[tabBarController setViewControllers:@[
firstNavigationController,
secondNavigationController,
]];
self.tabBarController = tabBarController;
}
/*
*
在`-setViewControllers:`之前设置TabBar的属性,
*
*/
- (void)customizeTabBarForController:(CYLTabBarController *)tabBarController {
NSDictionary *dict1 = @{
CYLTabBarItemTitle : @"首页",
CYLTabBarItemImage : @"home_normal",
CYLTabBarItemSelectedImage : @"home_highlight",
};
NSDictionary *dict2 = @{
CYLTabBarItemTitle : @"同城",
CYLTabBarItemImage : @"mycity_normal",
CYLTabBarItemSelectedImage : @"mycity_highlight",
};
NSArray *tabBarItemsAttributes = @[ dict1, dict2 ];
tabBarController.tabBarItemsAttributes = tabBarItemsAttributes;
}
第三步:将CYLTabBarController设置为window的RootViewController
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* *省略部分: * */
[self.window setRootViewController:self.tabBarController];
/* *省略部分: * */
return YES;
}
第四步(可选):创建自定义的形状不规则加号按钮
创建一个继承于 CYLPlusButton 的类,要求和步骤:
-
实现
CYLPlusButtonSubclassing
协议 -
子类将自身类型进行注册,一般可在
application
的applicationDelegate
方法里面调用[YourClass registerSubClass]
或者在子类的+load
方法中调用:
+(void)load {
[super registerSubclass];
}
协议提供了可选方法:
+ (NSUInteger)indexOfPlusButtonInTabBar;
+ (CGFloat)multiplerInCenterY;
+ (UIViewController *)plusChildViewController;
作用分别是:
+ (NSUInteger)indexOfPlusButtonInTabBar;
用来自定义加号按钮的位置,如果不实现默认居中,但是如果 tabbar
的个数是奇数则必须实现该方法,否则 CYLTabBarController
会抛出 exception
来进行提示。
主要适用于如下情景:
enter image description hereAirbnb-app效果:
enter image description here+ (CGFloat)multiplerInCenterY;
该方法是为了调整自定义按钮中心点Y轴方向的位置,建议在按钮超出了 tabbar
的边界时实现该方法。返回值是自定义按钮中心点Y轴方向的坐标除以 tabbar
的高度,如果不实现,会自动进行比对,预设一个较为合适的位置,如果实现了该方法,预设的逻辑将失效。
详见Demo中的 CYLPlusButtonSubclass
类的实现。
+ (UIViewController *)plusChildViewController;
详见: 点击 PlusButton 跳转到指定 UIViewController
另外,如果加号按钮超出了边界,一般需要手动调用如下代码取消 tabbar 顶部默认的阴影,可在 AppDelegate 类中调用:
//去除 TabBar 自带的顶部阴影
[[UITabBar appearance] setShadowImage:[[UIImage alloc] init]];
如何调整、自定义 PlusButton
与其它 TabBarItem
的宽度?
CYLTabBarController
规定:
TabBarItem 宽度 = ( TabBar 总宽度 - PlusButton 宽度 ) / (TabBarItem 个数)
所以想自定义宽度,只需要修改 PlusButton
的宽度即可。
比如你就可以在 Demo中的 CYLPlusButtonSubclass.m
类里:
把
[button sizeToFit];
改为
button.frame = CGRectMake(0.0, 0.0, 250, 100);
button.backgroundColor = [UIColor redColor];
效果如下,
enter image description here同时你也可以顺便测试下 CYLTabBarController
的这一个特性:
即使加号按钮超出了tabbar的区域,超出部分依然能响应点击事件
并且你可以在项目中的任意位置读取到 PlusButton
的宽度,借助 CYLTabBarController.h
定义的 CYLPlusButtonWidth
这个extern。可参考 +[CYLTabBarControllerConfig customizeTabBarAppearance:]
里的用法。
补充说明
自定义 TabBar
样式
如果想更进一步的自定义 TabBar
样式可在 -application:didFinishLaunchingWithOptions:
方法中设置
/**
* tabBarItem 的选中和不选中文字属性、背景图片
*/
- (void)customizeInterface {
// 普通状态下的文字属性
NSMutableDictionary *normalAttrs = [NSMutableDictionary dictionary];
normalAttrs[NSForegroundColorAttributeName] = [UIColor grayColor];
// 选中状态下的文字属性
NSMutableDictionary *selectedAttrs = [NSMutableDictionary dictionary];
selectedAttrs[NSForegroundColorAttributeName] = [UIColor darkGrayColor];
// 设置文字属性
UITabBarItem *tabBar = [UITabBarItem appearance];
[tabBar setTitleTextAttributes:normalAttrs forState:UIControlStateNormal];
[tabBar setTitleTextAttributes:selectedAttrs forState:UIControlStateSelected];
// 设置背景图片
UITabBar *tabBarAppearance = [UITabBar appearance];
[tabBarAppearance setBackgroundImage:[UIImage imageNamed:@"tabbar_background"]];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/* *省略部分: * */
[self.window makeKeyAndVisible];
[self customizeInterface];
return YES;
}
横竖屏适配
TabBar
横竖屏适配时,如果你添加了 PlusButton
,且适配时用到了 TabBarItem
的宽度, 不建议使用系统的UIDeviceOrientationDidChangeNotification
, 请使用库里的 CYLTabBarItemWidthDidChangeNotification
来更新 TabBar
布局,最典型的场景就是,根据 TabBarItem
在不同横竖屏状态下的宽度变化来切换选中的TabBarItem
的背景图片。Demo 里 CYLTabBarControllerConfig.m
给出了这一场景的用法:
CYLTabBarController.h
中提供了 CYLTabBarItemWidth
这一extern常量,并且会在 TabBarItem
的宽度发生变化时,及时更新该值,所以用法就如下所示:
- (void)updateTabBarCustomizationWhenTabBarItemWidthDidUpdate {
void (^deviceOrientationDidChangeBlock)(NSNotification *) = ^(NSNotification *notification) {
[self tabBarItemWidthDidUpdate];
};
[[NSNotificationCenter defaultCenter] addObserverForName:CYLTabBarItemWidthDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:deviceOrientationDidChangeBlock];
}
- (void)tabBarItemWidthDidUpdate {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if ((orientation == UIDeviceOrientationLandscapeLeft) || (orientation == UIDeviceOrientationLandscapeRight)) {
NSLog(@"Landscape Left or Right !");
} else if (orientation == UIDeviceOrientationPortrait){
NSLog(@"Landscape portrait!");
}
CGSize selectionIndicatorImageSize = CGSizeMake(CYLTabBarItemWidth, [self cyl_tabBarController].tabBar.bounds.size.height);
[[self cyl_tabBarController].tabBar setSelectionIndicatorImage:[[self class]
imageFromColor:[UIColor yellowColor]
forSize:selectionIndicatorImageSize
withCornerRadius:0]];
}
enter image description here
访问初始化好的 CYLTabBarController 对象
对于任意 NSObject
对象:
CYLTabBarController.h
中为 NSObject
提供了分类方法 -cyl_tabBarController
,所以在任意对象中,一行代码就可以访问到一个初始化好的 CYLTabBarController
对象,-cyl_tabBarController
的作用你可以这样理解:与获取单例对象的 +shareInstance
方法作用一样。
接口如下:
// CYLTabBarController.h
@interface NSObject (CYLTabBarController)
/**
* If `self` is kind of `UIViewController`, this method will return the nearest ancestor in the view controller hierarchy that is a tab bar controller. If `self` is not kind of `UIViewController`, it will return the `rootViewController` of the `rootWindow` as long as you have set the `CYLTabBarController` as the `rootViewController`. Otherwise return nil. (read-only)
*/
@property (nonatomic, readonly) CYLTabBarController *cyl_tabBarController;
@end
用法:
//导入 CYLTabBarController.h
#import "CYLTabBarController.h"
- (void)viewDidLoad {
[super viewDidLoad];
CYLTabBarController *tabbarController = [self cyl_tabBarController];
/*...*/
}
点击 PlusButton 跳转到指定 UIViewController
提供了一个协议方法来完成本功能:
enter image description here实现该方法后,能让 PlusButton 的点击效果与跟点击其他 UITabBarButton 效果一样,跳转到该方法指定的 UIViewController 。
注意:必须同时实现 +indexOfPlusButtonInTabBar
来指定 PlusButton 的位置。
遵循两个协议:
enter image description here让TabBarItem仅显示图标,并使图标垂直居中
要想实现该效果,只需要在设置 tabBarItemsAttributes
该属性时不传 title 即可。
比如:在Demo的基础上,注释掉图中红框部分:
enter image description here
注释前 | 注释后 |
---|---|
enter image description here | enter image description here |
可以通过这种方式来达到 Airbnb-app 的效果:
enter image description here如果想手动设置偏移量来达到该效果:
可以在 -setViewControllers:
方法前设置 CYLTabBarController
的 imageInsets
和 titlePositionAdjustment
属性
这里注意:设置这两个属性后,TabBar
中所有的 TabBarItem
都将被设置。并且第一种做法的逻辑将不会执行,也就是说该做法优先级要高于第一种做法。
做法如下:
enter image description here
但是想达到Airbnb-app的效果只有这个接口是不行的,还需要自定义下 TabBar
的高度,你需要设置 CYLTabBarController
的 tabBarHeight
属性。你可以在Demo的 CYLTabBarControllerConfig.m
中的 -customizeTabBarAppearance:
方法中设置。
注:“仅显示图标,并使图标垂直居中”这里所指的“图标”,其所属的类是私有类: UITabBarSwappableImageView
,所以 CYLTabBarController
在相关的接口命名时会包含 SwappableImageView
字样。
在 Swift 项目中使用 CYLTabBarController
参考: 《从头开始swift2.1 仿搜材通项目(三) 主流框架Tabbed的搭建》
这里注意,文章的示例代码有问题,少了设置 PlusButton 大小的代码:
这将导致 PlusButton 点击事件失效,具体修改代码如下:
enter image description here
源码实现原理
参考: 《[Note] CYLTabBarController》
更多文档信息可查看 CocoaDocs:CYLTabBarController 。
Q-A
Q:为什么放置6个TabBarItem会显示异常?
A:
Apple 规定:
一个
TabBar
上只能出现最多5个TabBarItem
,第六个及更多的将不被显示。
另外注意,Apple检测的是 UITabBarItem
及其子类,所以放置“加号按钮”,这是 UIButton
不在“5个”里面。
最多只能添加5个 TabBarItem
,也就是说加上“加号按钮”,一共最多在一个 TabBar
上放置6个控件。否则第6个及之后出现 TabBarItem
会被自动屏蔽掉。而且就Apple的审核机制来说,超过5个也会被直接拒绝上架。
Q: 如何实现添加选中背景色的功能 ,像下面这样:
enter image description here
A: 这个是 iOS 系统的BUG,经测试iOS9.3已经修复了,如果在更早起版本中出现了,可以通过下面将 rootWindow
的背景色改为白色来避免:比如你可以 Appdelegate
类里这样设置:
//#import "CYLTabBarController.h"
[[self cyl_tabBarController] rootWindow].backgroundColor = [UIColor whiteColor];
Q:我现在已经做好了一个比较简单的中间凸起的 icon 但是超过了49这个高度的位置是不能效应的 我想请问你的demo哪个功能是可以使我超出的范围也可以响应的呢?
A: 这个是自动做的,但是 CYLTabBarController
只能保证的是:只要是 UIButton
的 frame 区域内就能响应。
请把 button 的背景颜色设置为显眼的颜色,比如红色,比如像下面的plus按钮,红色部分是能接收点击事件的,但是超出了红色按钮的,黄色的图片区域,依然是无法响应点击事件的。
enter image description here这是因为,在响应链上,UIControl
能响应点击事件, UIImage
无法响应。
(更多iOS开发干货,欢迎关注 微博@iOS程序犭袁 )
Posted by 微博@iOS程序犭袁
原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
<p align="center"><a href="http://weibo.com/u/1692391497?s=6uyXnP" target="_blank">
网友评论
///中间的资金按钮
class PlusButtonSubclass : CYLPlusButton,CYLPlusButtonSubclassing{
class func plusButton() -> AnyObject! {
let button:PlusButtonSubclass = PlusButtonSubclass()
button.setImage(UIImage(named: "zhuangtai"), forState: UIControlState.Normal)
button.titleLabel!.textAlignment = NSTextAlignment.Center;
button.adjustsImageWhenHighlighted = false;
button.addTarget(button, action: #selector(PlusButtonSubclass.buttonClicked), forControlEvents: UIControlEvents.TouchUpInside)
return button
}
//点击事件
func buttonClicked()
{
print("hello")
}
点击事件没响应,求指教!在线等