UIBarButtonItem设置Badge

简介

UIBarButtonItem 默认是不能 设置 Badge 所以我们可以对它扩展使之支持

实现方法

添加如下代码

UIBarButtonItem+Badge.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import <UIKit/UIKit.h>

@interface UIBarButtonItem (Badge)

@property (strong, nonatomic) UILabel *badge;

// Badge value to be display
@property (nonatomic) NSString *badgeValue;
// Badge background color
@property (nonatomic) UIColor *badgeBGColor;
// Badge text color
@property (nonatomic) UIColor *badgeTextColor;
// Badge font
@property (nonatomic) UIFont *badgeFont;
// Padding value for the badge
@property (nonatomic) CGFloat badgePadding;
// Minimum size badge to small
@property (nonatomic) CGFloat badgeMinSize;
// Values for offseting the badge over the BarButtonItem you picked
@property (nonatomic) CGFloat badgeOriginX;
@property (nonatomic) CGFloat badgeOriginY;
// In case of numbers, remove the badge when reaching zero
@property BOOL shouldHideBadgeAtZero;
// Badge has a bounce animation when value changes
@property BOOL shouldAnimateBadge;

@end

UIBarButtonItem+Badge.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#import <objc/runtime.h>
#import "UIBarButtonItem+Badge.h"

NSString const *badgeKey = @"badgeKey";

NSString const *badgeBGColorKey = @"badgeBGColorKey";
NSString const *badgeTextColorKey = @"badgeTextColorKey";
NSString const *badgeFontKey = @"badgeFontKey";
NSString const *badgePaddingKey = @"badgePaddingKey";
NSString const *badgeMinSizeKey = @"badgeMinSizeKey";
NSString const *badgeOriginXKey = @"badgeOriginXKey";
NSString const *badgeOriginYKey = @"badgeOriginYKey";
NSString const *shouldHideBadgeAtZeroKey = @"shouldHideBadgeAtZeroKey";
NSString const *shouldAnimateBadgeKey = @"shouldAnimateBadgeKey";
NSString const *badgeValueKey = @"badgeValueKey";

@implementation UIBarButtonItem (Badge)

@dynamic badgeValue, badgeBGColor, badgeTextColor, badgeFont;
@dynamic badgePadding, badgeMinSize, badgeOriginX, badgeOriginY;
@dynamic shouldHideBadgeAtZero, shouldAnimateBadge;

- (void)badgeInit
{
// Default design initialization
self.badgeBGColor = [UIColor redColor];
self.badgeTextColor = [UIColor whiteColor];
self.badgeFont = [UIFont systemFontOfSize:10.0];
self.badgePadding = 4;
self.badgeMinSize = 6;
self.badgeOriginX = self.customView.frame.size.width - self.badge.frame.size.width/2;
self.badgeOriginY = -6;
self.shouldHideBadgeAtZero = YES;
self.shouldAnimateBadge = YES;
// Avoids badge to be clipped when animating its scale
self.customView.clipsToBounds = NO;
}

#pragma mark - Utility methods

// Handle badge display when its properties have been changed (color, font, ...)
- (void)refreshBadge
{
// Change new attributes
self.badge.textColor = self.badgeTextColor;
self.badge.backgroundColor = self.badgeBGColor;
self.badge.font = self.badgeFont;
}

- (CGSize) badgeExpectedSize
{
// When the value changes the badge could need to get bigger
// Calculate expected size to fit new value
// Use an intermediate label to get expected size thanks to sizeToFit
// We don‘t call sizeToFit on the true label to avoid bad display
UILabel *frameLabel = [self duplicateLabel:self.badge];
[frameLabel sizeToFit];

CGSize expectedLabelSize = frameLabel.frame.size;
return expectedLabelSize;
}

- (void)updateBadgeFrame
{

CGSize expectedLabelSize = [self badgeExpectedSize];

// Make sure that for small value, the badge will be big enough
CGFloat minHeight = expectedLabelSize.height;

// Using a const we make sure the badge respect the minimum size
minHeight = (minHeight < self.badgeMinSize) ? self.badgeMinSize : expectedLabelSize.height;
CGFloat minWidth = expectedLabelSize.width;
CGFloat padding = self.badgePadding;

// Using const we make sure the badge doesn‘t get too smal
minWidth = (minWidth < minHeight) ? minHeight : expectedLabelSize.width;
self.badge.frame = CGRectMake(self.badgeOriginX, self.badgeOriginY, minWidth + padding, minHeight + padding);
self.badge.layer.cornerRadius = (minHeight + padding) / 2;
self.badge.layer.masksToBounds = YES;
}

// Handle the badge changing value
- (void)updateBadgeValueAnimated:(BOOL)animated
{
// Bounce animation on badge if value changed and if animation authorized
if (animated && self.shouldAnimateBadge && ![self.badge.text isEqualToString:self.badgeValue]) {
CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
[animation setFromValue:[NSNumber numberWithFloat:1.5]];
[animation setToValue:[NSNumber numberWithFloat:1]];
[animation setDuration:0.2];
[animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.4f :1.3f :1.f :1.f]];
[self.badge.layer addAnimation:animation forKey:@"bounceAnimation"];
}

// Set the new value
self.badge.text = self.badgeValue;

// Animate the size modification if needed
NSTimeInterval duration = animated ? 0.2 : 0;
[UIView animateWithDuration:duration animations:^{
[self updateBadgeFrame];
}];
}

- (UILabel *)duplicateLabel:(UILabel *)labelToCopy
{
UILabel *duplicateLabel = [[UILabel alloc] initWithFrame:labelToCopy.frame];
duplicateLabel.text = labelToCopy.text;
duplicateLabel.font = labelToCopy.font;

return duplicateLabel;
}

- (void)removeBadge
{
// Animate badge removal
[UIView animateWithDuration:0.2 animations:^{
self.badge.transform = CGAffineTransformMakeScale(0, 0);
} completion:^(BOOL finished) {
[self.badge removeFromSuperview];
self.badge = nil;
}];
}

#pragma mark - getters/setters
-(UILabel*) badge {
return objc_getAssociatedObject(self, &badgeKey);
}
-(void)setBadge:(UILabel *)badgeLabel
{
objc_setAssociatedObject(self, &badgeKey, badgeLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// Badge value to be display
-(NSString *)badgeValue {
return objc_getAssociatedObject(self, &badgeValueKey);
}
-(void) setBadgeValue:(NSString *)badgeValue
{
objc_setAssociatedObject(self, &badgeValueKey, badgeValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

// When changing the badge value check if we need to remove the badge
if (!badgeValue || [badgeValue isEqualToString:@""] || ([badgeValue isEqualToString:@"0"] && self.shouldHideBadgeAtZero)) {
[self removeBadge];
} else if (!self.badge) {
// Create a new badge because not existing
self.badge = [[UILabel alloc] initWithFrame:CGRectMake(self.badgeOriginX, self.badgeOriginY, 20, 20)];
self.badge.textColor = self.badgeTextColor;
self.badge.backgroundColor = self.badgeBGColor;
self.badge.font = self.badgeFont;
self.badge.textAlignment = NSTextAlignmentCenter;
[self badgeInit];
[self.customView addSubview:self.badge];
[self updateBadgeValueAnimated:NO];
} else {
[self updateBadgeValueAnimated:YES];
}
}

// Badge background color
-(UIColor *)badgeBGColor {
return objc_getAssociatedObject(self, &badgeBGColorKey);
}
-(void)setBadgeBGColor:(UIColor *)badgeBGColor
{
objc_setAssociatedObject(self, &badgeBGColorKey, badgeBGColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}

// Badge text color
-(UIColor *)badgeTextColor {
return objc_getAssociatedObject(self, &badgeTextColorKey);
}
-(void)setBadgeTextColor:(UIColor *)badgeTextColor
{
objc_setAssociatedObject(self, &badgeTextColorKey, badgeTextColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}

// Badge font
-(UIFont *)badgeFont {
return objc_getAssociatedObject(self, &badgeFontKey);
}

-(void)setBadgeFont:(UIFont *)badgeFont
{
objc_setAssociatedObject(self, &badgeFontKey, badgeFont, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}

// Padding value for the badge
-(CGFloat) badgePadding {
NSNumber *number = objc_getAssociatedObject(self, &badgePaddingKey);
return number.floatValue;
}
-(void) setBadgePadding:(CGFloat)badgePadding
{
NSNumber *number = [NSNumber numberWithDouble:badgePadding];
objc_setAssociatedObject(self, &badgePaddingKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self updateBadgeFrame];
}
}

// Minimum size badge to small
-(CGFloat) badgeMinSize {
NSNumber *number = objc_getAssociatedObject(self, &badgeMinSizeKey);
return number.floatValue;
}
-(void) setBadgeMinSize:(CGFloat)badgeMinSize
{
NSNumber *number = [NSNumber numberWithDouble:badgeMinSize];
objc_setAssociatedObject(self, &badgeMinSizeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self updateBadgeFrame];
}
}

// Values for offseting the badge over the BarButtonItem you picked
-(CGFloat) badgeOriginX {
NSNumber *number = objc_getAssociatedObject(self, &badgeOriginXKey);
return number.floatValue;
}

-(void) setBadgeOriginX:(CGFloat)badgeOriginX
{
NSNumber *number = [NSNumber numberWithDouble:badgeOriginX];
objc_setAssociatedObject(self, &badgeOriginXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self updateBadgeFrame];
}
}

-(CGFloat) badgeOriginY {
NSNumber *number = objc_getAssociatedObject(self, &badgeOriginYKey);
return number.floatValue;
}
-(void) setBadgeOriginY:(CGFloat)badgeOriginY
{
NSNumber *number = [NSNumber numberWithDouble:badgeOriginY];
objc_setAssociatedObject(self, &badgeOriginYKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self updateBadgeFrame];
}
}

// In case of numbers, remove the badge when reaching zero
-(BOOL) shouldHideBadgeAtZero {
NSNumber *number = objc_getAssociatedObject(self, &shouldHideBadgeAtZeroKey);
return number.boolValue;
}

- (void)setShouldHideBadgeAtZero:(BOOL)shouldHideBadgeAtZero
{
NSNumber *number = [NSNumber numberWithBool:shouldHideBadgeAtZero];
objc_setAssociatedObject(self, &shouldHideBadgeAtZeroKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// Badge has a bounce animation when value changes
-(BOOL) shouldAnimateBadge {
NSNumber *number = objc_getAssociatedObject(self, &shouldAnimateBadgeKey);
return number.boolValue;
}
- (void)setShouldAnimateBadge:(BOOL)shouldAnimateBadge
{
NSNumber *number = [NSNumber numberWithBool:shouldAnimateBadge];
objc_setAssociatedObject(self, &shouldAnimateBadgeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

调用方法

初始化

1
2
3
4
5
6
7
8
9
let rightBarButtonItemImage:UIImage! = UIImage(named: "tongzhi");
let button = UIButton(type: UIButtonType.Custom);
button.frame = CGRectMake(0,0,rightBarButtonItemImage.size.width, rightBarButtonItemImage.size.height);
button.setBackgroundImage(rightBarButtonItemImage, forState: UIControlState.Normal);
button.addTarget(self, action: #selector(MainViewController.tongzhiPressed(_:)), forControlEvents: UIControlEvents.TouchUpInside);
let navRightButton = UIBarButtonItem(customView: button);
navRightButton.badgeBGColor = UIColor.redColor();
navRightButton.badgeTextColor = UIColor.whiteColor();
self.navigationItem.rightBarButtonItem = navRightButton;

设置值

1
self.navigationItem.rightBarButtonItem!.badgeValue = "2";

设置隐藏

1
self.navigationItem.rightBarButtonItem!.badgeValue = "";

其它组件

也可以用其它组件实现
比如的这个JSBadgeView
它可以在任何View上添加Badge

引用方式import JSBadgeView

调用方式

1
2
let badgeView = JSBadgeView(parentView: myview, alignment: JSBadgeViewAlignment.TopRight);
badgeView.badgeText = "3";