上次是将tabbar上的button进行修改和封装,同时显示了view controller的代理。看一下上次的效果图:
那么下面就来做提醒数字,提醒数字其实就是这种效果:
按钮图标右上角的小圆圈就是提醒数字的按钮。
首先需要创建4个这种的按钮:
在QLDTabBarViewController里面:
/**
* 初始化所有子控制器
*/
- (void) setupAllChildViewControllers
{
//1.首页
QLDHomeTableViewController *home = [[QLDHomeTableViewController alloc]init];
home.tabBarItem.badgeValue = @"7";
[self setupChildViewController: home title: @"首页" imageName:@"tabbar_home" selectedImageName:@"tabbar_home_selected"];
//2.消息
QLDMessageTableViewController *message = [[QLDMessageTableViewController alloc]init];
message.tabBarItem.badgeValue = @"N";
[self setupChildViewController: message title: @"消息" imageName:@"tabbar_message_center" selectedImageName:@"tabbar_message_center_selected"];
//3.广场
QLDDiscoverTableViewController *discover = [[QLDDiscoverTableViewController alloc]init];
discover.tabBarItem.badgeValue = @"80";
[self setupChildViewController: discover title: @"广场" imageName:@"tabbar_discover" selectedImageName:@"tabbar_discover_selected"];
//4.我
QLDMeTableViewController *me = [[QLDMeTableViewController alloc]init];
me.tabBarItem.badgeValue = @"5";
[self setupChildViewController: me title: @"我" imageName:@"tabbar_profile" selectedImageName:@"tabbar_profile_selected"];
}
添加提醒数字的值,可以是数字,也可以是字符串。
然后在QLDTabBarButton里面:
@interface QLDTabBarButton ()
/**
* 提醒数字
*/
@property (nonatomic, weak) UIButton *badgeButton;
@end
然后在.m文件中创建
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 设置图片居中
self.imageView.contentMode = UIViewContentModeCenter;
// 设置文字居中
self.titleLabel.textAlignment = NSTextAlignmentCenter;
// 调节字体
self.titleLabel.font = [UIFont systemFontOfSize:11];
// 设置字体颜色
[self setTitleColor: QLDTabBarButtonTitleColor forState:UIControlStateNormal];
[self setTitleColor:QLDTabBarButtonTitleSelectedColor forState:UIControlStateSelected];
if (!iOS7) { //如果不是ios7,那么需要这个button背景
//设置按钮被选中时的背景图片
[self setBackgroundImage:[UIImage imageWithName:@"tabbar_slider"] forState:UIControlStateSelected];
}
//添加一个提醒数字按钮
UIButton *badgeButton = [[UIButton alloc] init];
//先要隐藏起来
//设置提醒数字的背景图片,因为只有这一张,所以直接在这里设置
[badgeButton setBackgroundImage:[UIImage imageWithName:@"main_badge"] forState:UIControlStateNormal];
[self addSubview:badgeButton];
self.badgeButton = badgeButton;
}
return self;
}
-(void)setItem:(UITabBarItem *)item
{
_item = item;
//设置文字
[self setTitle:item.title forState:UIControlStateNormal];
//设置图片
[self setImage:item.image forState:UIControlStateNormal];
[self setImage:item.selectedImage forState:UIControlStateSelected];
//设置提醒数字
if (item.badgeValue) {//如果有值,那么显示出来
self.badgeButton.hidden = NO;
//设置文字
[self.badgeButton setTitle:item.badgeValue forState:UIControlStateNormal];
//按钮的尺寸必须在这里设置,因为这里是可以根据提醒数字里的值来计算的
CGFloat badgeY = 5;
CGFloat badgeH = self.badgeButton.currentBackgroundImage.size.height;
//计算文字尺寸
CGSize badgeSize = [item.badgeValue sizeWithFont:self.badgeButton.titleLabel.font];
CGFloat badgeW = badgeSize.width + 10;
CGFloat badgeX = self.frame.size.width - badgeW - 5;
self.badgeButton.frame = CGRectMake(badgeX, badgeY, badgeW, badgeH);
}
else{//如果没有值,那么就隐藏
self.badgeButton.hidden = YES;
}
}
这个尺寸的计算比较繁琐,主要就是根据原始图片来做,y的话可以随意定一个,h就是原始图片的高度,但是宽度是根据里面的提醒数字来计算出来的,x就是按钮的宽度减去本身的宽度再减去一个数。
运行一下,看一下效果图:
这里可以看到少了一个提醒数字,还有这个里面的数字有点大,而且拉伸的时候会把字也拉伸了。
少了一个提醒数字的原因是:
CGFloat badgeX = self.frame.size.width - badgeW - 5;
这句话,因为 self.frame.size.width 是等于0,所以x是负数,所以后面的就往前移了,self.frame.size.width = 0,是因为
- (void) addTabBarButtonWithItem: (UITabBarItem *)item
{
//1.创建按钮,将button加载到QLDTabBar上
QLDTabBarButton *button = [[QLDTabBarButton alloc] init];
[self addSubview:button];
//2.设置按钮的数据
button.item = item;
//3.监听按钮的点击
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchDown];
//4.默认选中第0个按钮
if (self.subviews.count == 1) {
[self buttonClick:button];
}
}
这里创建了button,但是是没有frame的,所以才会等于0
这里需要修改一下,badgeButton应该是当按钮有一定宽度之后,他的左边和下边进行拉伸,距离上边和右边的宽度是一定的。
badgeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin;
将代码放在initWithFrame里面。
修改文字大小,也是在initWithFrame 里面:
//设置提醒数字里面的字的大小
badgeButton.titleLabel.font = [UIFont systemFontOfSize:11];
接下去修改拉伸的方式,就是需要保护原始图片的左边和右边形状不变,中间随着字数的尺寸而改变:
在UIImage (QLD) 申明:
/**
* 返回一张自由拉伸的图片
* @param name 图片名
*/
+ (UIImage *)resizedImageWithName: (NSString *)name;
在.m文件中实现方法:
+ (UIImage *)resizedImageWithName: (NSString *)name
{
UIImage *image = [self imageWithName:name];
// 返回一张左右两边都被保护的图片
return [image stretchableImageWithLeftCapWidth:image.size.width * 0.5 topCapHeight:image.size.width * 0.5];
}
还有一个问题,就是当提醒数字里面的内容只有一个数,那么按照上面的方法的话,就会出现比原始图片还要小的提醒数字按钮,所以需要改一下:
-(void)setItem:(UITabBarItem *)item
{
_item = item;
//设置文字
[self setTitle:item.title forState:UIControlStateNormal];
//设置图片
[self setImage:item.image forState:UIControlStateNormal];
[self setImage:item.selectedImage forState:UIControlStateSelected];
//设置提醒数字
if (item.badgeValue) {//如果有值,那么显示出来
self.badgeButton.hidden = NO;
//设置文字
[self.badgeButton setTitle:item.badgeValue forState:UIControlStateNormal];
//按钮的尺寸必须在这里设置,因为这里是可以根据提醒数字里的值来计算的
CGFloat badgeY = 5;
CGFloat badgeH = self.badgeButton.currentBackgroundImage.size.height;
//默认情况下,宽度为原始图片的宽度
CGFloat badgeW = self.badgeButton.currentBackgroundImage.size.width;
if (item.badgeValue.length > 1) {//如果字符数>1那么就根据文字尺寸来计算宽度
//计算文字尺寸
CGSize badgeSize = [item.badgeValue sizeWithFont:self.badgeButton.titleLabel.font];
badgeW = badgeSize.width + 10;
}
CGFloat badgeX = self.frame.size.width - badgeW - 10;
self.badgeButton.frame = CGRectMake(badgeX, badgeY, badgeW, badgeH);
}
else{//如果没有值,那么就隐藏
self.badgeButton.hidden = YES;
}
}
运行一下,看效果:
不过因为提醒数字是按钮类型的,所以它是可以点击的,这个效果需要取消,应该为不能点击:
在initWithFrame里面应该加入:
//设置不能点击
badgeButton.userInteractionEnabled = NO;
运行之后就这样:
不能点击了。
虽然做完了这个,但是由于这个其实是在一开始就创建了的,但是在程序运行过程中,很有可能会随之而改变它的值,所以需要进行监听,使用KVO:
要实现这个,可以先弄一个按钮,点击之后,能够使tabbar上的提醒数字、图标、标题等都能随之改变,这样在程序中就不是定死的了,而是可以变动的,再接下去可以将其封装起来,这样调用,改变就能更快速。
1.首先在QLDHomeTableViewController里面创建一个按钮,在按钮上添加事件。
-(void)viewDidLoad
{
[super viewDidLoad];
//创建一个按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn.center = CGPointMake(100, 200);
//添加点击事件
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
/*
* 点击按钮发生事件
*/
- (void)btnClick
{
self.tabBarItem.badgeValue = @"ns";
}
运行结果如下:
点击按钮,无法改变提醒数字的内容,这里需要监听控制器的item的badgeValue的值的改变。
2.监听属性改变:
在QLDTabBarButton中:
-(void)setItem:(UITabBarItem *)item
{
_item = item;
// KVO 监听属性改变
[item addObserver:self forKeyPath:@"badgeValue" options:0 context:nil];
}
/**
* 监听到某个对象的属性改变就会调用
* @param keyPath 属性名
* @param object 哪个对象的属性被改变
* @param change 属性发生的改变
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"-----%@的%@属性被修改",object,keyPath);
//设置文字
[self setTitle:self.item.title forState:UIControlStateNormal];
//设置图片
[self setImage:self.item.image forState:UIControlStateNormal];
[self setImage:self.item.selectedImage forState:UIControlStateSelected];
//设置提醒数字
if (self.item.badgeValue) {//如果有值,那么显示出来
self.badgeButton.hidden = NO;
//设置文字
[self.badgeButton setTitle:self.item.badgeValue forState:UIControlStateNormal];
//按钮的尺寸必须在这里设置,因为这里是可以根据提醒数字里的值来计算的
CGFloat badgeY = 5;
CGFloat badgeH = self.badgeButton.currentBackgroundImage.size.height;
//默认情况下,宽度为原始图片的宽度
CGFloat badgeW = self.badgeButton.currentBackgroundImage.size.width;
if (self.item.badgeValue.length > 1) {//如果字符数>1那么就根据文字尺寸来计算宽度
//计算文字尺寸
CGSize badgeSize = [self.item.badgeValue sizeWithFont:self.badgeButton.titleLabel.font];
badgeW = badgeSize.width + 10;
}
CGFloat badgeX = self.frame.size.width - badgeW - 10;
self.badgeButton.frame = CGRectMake(badgeX, badgeY, badgeW, badgeH);
}
else{//如果没有值,那么就隐藏
self.badgeButton.hidden = YES;
}
}
运行一下,效果图:
点击之后出现第一个按钮,其他按钮是白色的原因是属性没有改变所以不能在监听里面进行加载,需要改一下,另外把title,image都进行监听:
-(void)setItem:(UITabBarItem *)item
{
_item = item;
// KVO 监听属性改变
//监听badgeValue
[item addObserver:self forKeyPath:@"badgeValue" options:0 context:nil];
//监听title
[item addObserver:self forKeyPath:@"title" options:0 context:nil];
//监听image
[item addObserver:self forKeyPath:@"image" options:0 context:nil];
//监听selectedImage
[item addObserver:self forKeyPath:@"selectedImage" options:0 context:nil];
//加载4个button
[self observeValueForKeyPath:nil ofObject:nil change:nil context:nil];
}
/**
* 监听到某个对象的属性改变就会调用
* @param keyPath 属性名
* @param object 哪个对象的属性被改变
* @param change 属性发生的改变
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
// NSLog(@"-----%@的%@属性被修改",object,keyPath);
//设置文字
[self setTitle:self.item.title forState:UIControlStateNormal];
[self setTitle:self.item.title forState:UIControlStateSelected];
//设置图片
[self setImage:self.item.image forState:UIControlStateNormal];
[self setImage:self.item.selectedImage forState:UIControlStateSelected];
//设置提醒数字
if (self.item.badgeValue) {//如果有值,那么显示出来
self.badgeButton.hidden = NO;
//设置文字
[self.badgeButton setTitle:self.item.badgeValue forState:UIControlStateNormal];
//按钮的尺寸必须在这里设置,因为这里是可以根据提醒数字里的值来计算的
CGFloat badgeY = 5;
CGFloat badgeH = self.badgeButton.currentBackgroundImage.size.height;
//默认情况下,宽度为原始图片的宽度
CGFloat badgeW = self.badgeButton.currentBackgroundImage.size.width;
if (self.item.badgeValue.length > 1) {//如果字符数>1那么就根据文字尺寸来计算宽度
//计算文字尺寸
CGSize badgeSize = [self.item.badgeValue sizeWithFont:self.badgeButton.titleLabel.font];
badgeW = badgeSize.width + 10;
}
CGFloat badgeX = self.frame.size.width - badgeW - 10;
self.badgeButton.frame = CGRectMake(badgeX, badgeY, badgeW, badgeH);
}
else{//如果没有值,那么就隐藏
self.badgeButton.hidden = YES;
}
}
运行一下,看效果:
可以看到,这样就可以进行更改了。
还有重要的一点就是关于KVO的释放(必须要移除!就像通知):
-(void)dealloc
{
[self.item removeObserver:self forKeyPath:@"badgeValue"];
[self.item removeObserver:self forKeyPath:@"title"];
[self.item removeObserver:self forKeyPath:@"image"];
[self.item removeObserver:self forKeyPath:@"selectedImage"];
}
3.进行封装一下:
创建一个继承自UIButton的QLDBadgeButton类:
在监听方法中,设置提醒数字里面的部分是可以封装到QLDBadgeButton里面的,但是提醒数字的x,y是不能放到QLDBadgeButton,因为它不是由QLDBadgeButton自己就能决定的,而是要QLDTabBarButton决定的,所以需要改这些地方:
#import "QLDTabBarButton.h"
#import "QLDBadgeButton.h"
@interface QLDTabBarButton ()
/**
* 提醒数字
*/
@property (nonatomic, weak) QLDBadgeButton *badgeButton;
@end
在initWithFrame方法中也要改:
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 设置图片居中
self.imageView.contentMode = UIViewContentModeCenter;
// 设置文字居中
self.titleLabel.textAlignment = NSTextAlignmentCenter;
// 调节字体
self.titleLabel.font = [UIFont systemFontOfSize:11];
// 设置字体颜色
[self setTitleColor: QLDTabBarButtonTitleColor forState:UIControlStateNormal];
[self setTitleColor:QLDTabBarButtonTitleSelectedColor forState:UIControlStateSelected];
if (!iOS7) { //如果不是ios7,那么需要这个button背景
//设置按钮被选中时的背景图片
[self setBackgroundImage:[UIImage imageWithName:@"tabbar_slider"] forState:UIControlStateSelected];
}
//添加一个提醒数字按钮
QLDBadgeButton *badgeButton = [[QLDBadgeButton alloc] init];
//当badgeButton有frame之后,使其左边和下边进行拉伸,距离上边和右边距离是一定的
badgeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:badgeButton];
self.badgeButton = badgeButton;
}
return self;
}
还要改监听方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
// NSLog(@"-----%@的%@属性被修改",object,keyPath);
//设置文字
[self setTitle:self.item.title forState:UIControlStateNormal];
[self setTitle:self.item.title forState:UIControlStateSelected];
//设置图片
[self setImage:self.item.image forState:UIControlStateNormal];
[self setImage:self.item.selectedImage forState:UIControlStateSelected];
//设置提醒数字
self.badgeButton.badgeValue = self.item.badgeValue;
//设置提醒数字的位置(因为x,y是由这个控制器决定的,但是w,h是由QLDBadgeButton决定的)
CGFloat badgeY = 5;
CGFloat badgeX = self.frame.size.width - self.badgeButton.frame.size.width - 10;
CGRect badgeF = self.badgeButton.frame;
badgeF.origin.x = badgeX;
badgeF.origin.y = badgeY;
self.badgeButton.frame = badgeF;
}
这里这么做,是定义了一个属性在QLDBadgeButton里面,badgeValue,这个能够方便传值
在.m文件中,需要设置frame,以及重写setBadgeValue方法:
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//先要隐藏起来
self.hidden = YES;
//设置提醒数字的背景图片,因为只有这一张,所以直接在这里设置
[self setBackgroundImage:[UIImage resizedImageWithName:@"main_badge"] forState:UIControlStateNormal];
//设置不能点击
self.userInteractionEnabled = NO;
//设置提醒数字里面的字的大小
self.titleLabel.font = [UIFont systemFontOfSize:11];
}
return self;
}
//重写set方法
-(void)setBadgeValue:(NSString *)badgeValue
{
_badgeValue = [badgeValue copy];
if (badgeValue) {//如果有值,那么显示出来
self.hidden = NO;
//设置文字
[self setTitle:badgeValue forState:UIControlStateNormal];
//设置frame
CGRect frame = self.frame;
//按钮的尺寸必须在这里设置,因为这里是可以根据提醒数字里的值来计算的
CGFloat badgeH = self.currentBackgroundImage.size.height;
//默认情况下,宽度为原始图片的宽度
CGFloat badgeW = self.currentBackgroundImage.size.width;
if (badgeValue.length > 1) {//如果字符数>1那么就根据文字尺寸来计算宽度
//计算文字尺寸
CGSize badgeSize = [badgeValue sizeWithFont:self.titleLabel.font];
badgeW = badgeSize.width + 10;
}
frame.size.width = badgeW;
frame.size.height = badgeH;
self.frame = frame;
}
else{//如果没有值,那么就隐藏
self.hidden = YES;
}
}
运行一下,看效果:
可以看到,效果还是一样可以实现。
接下来可以看一下封装之后的好处:
在QLDHomeTableViewController.m里面:
#import "QLDHomeTableViewController.h"
#import "QLDBadgeButton.h"
@implementation QLDHomeTableViewController
-(void)viewDidLoad
{
[super viewDidLoad];
//创建一个按钮
UIButton *btn = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn.center = CGPointMake(100, 200);
//添加点击事件
[btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
//添加一个提醒数字
QLDBadgeButton *badge = [[QLDBadgeButton alloc] init];
badge.badgeValue = @"100";
badge.center = CGPointMake(200, 100);
[self.view addSubview:badge];
}
运行一下,看效果:
这种封装的效果是非常方便的,只需要在创建的时候告诉其数字和位置就可以完成创建,效果很明显。