forked from mikeMTOL/UIBarButtonItem-Badge
-
Notifications
You must be signed in to change notification settings - Fork 5
/
UIBarButtonItem+Badge.m
323 lines (280 loc) · 11.1 KB
/
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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//
// UIBarButtonItem+Badge.m
// therichest
//
// Created by Mike on 2014-05-05.
// Copyright (c) 2014 Valnet Inc. All rights reserved.
//
#import <objc/runtime.h>
#import "UIBarButtonItem+Badge.h"
#import <KVOController/FBKVOController.h>
NSString const *UIBarButtonItem_badgeKey = @"UIBarButtonItem_badgeKey";
NSString const *UIBarButtonItem_badgeBGColorKey = @"UIBarButtonItem_badgeBGColorKey";
NSString const *UIBarButtonItem_badgeTextColorKey = @"UIBarButtonItem_badgeTextColorKey";
NSString const *UIBarButtonItem_badgeFontKey = @"UIBarButtonItem_badgeFontKey";
NSString const *UIBarButtonItem_badgePaddingKey = @"UIBarButtonItem_badgePaddingKey";
NSString const *UIBarButtonItem_badgeMinSizeKey = @"UIBarButtonItem_badgeMinSizeKey";
NSString const *UIBarButtonItem_badgeOriginXKey = @"UIBarButtonItem_badgeOriginXKey";
NSString const *UIBarButtonItem_badgeOriginYKey = @"UIBarButtonItem_badgeOriginYKey";
NSString const *UIBarButtonItem_shouldHideBadgeAtZeroKey = @"UIBarButtonItem_shouldHideBadgeAtZeroKey";
NSString const *UIBarButtonItem_shouldAnimateBadgeKey = @"UIBarButtonItem_shouldAnimateBadgeKey";
NSString const *UIBarButtonItem_badgeValueKey = @"UIBarButtonItem_badgeValueKey";
@interface UIBarButtonItem ()
@property (strong, nonatomic, readwrite) UIView *view;
@end
@implementation UIBarButtonItem (Badge)
@dynamic badgeValue, badgeBGColor, badgeTextColor, badgeFont;
@dynamic badgePadding, badgeMinSize, badgeOriginX, badgeOriginY;
@dynamic shouldHideBadgeAtZero, shouldAnimateBadge;
- (void)badgeInit
{
[self badgeAddToView];
[self.KVOController unobserveAll];
[self.KVOController observe:self keyPath:@"view" options:NSKeyValueObservingOptionNew block:^(id observer, id object, NSDictionary *change) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self view]) {
[self badgeAddToView];
}
});
}];
}
- (void)badgeAddToView {
UIView *superview = nil;
CGFloat defaultOriginX = 0;
if (self.customView) {
superview = self.customView;
defaultOriginX = superview.frame.size.width - self.badge.frame.size.width/2;
// Avoids badge to be clipped when animating its scale
superview.clipsToBounds = NO;
} else if ([self respondsToSelector:@selector(view)] && [(id)self view]) {
superview = [(id)self view];
defaultOriginX = superview.frame.size.width - self.badge.frame.size.width;
}
[superview addSubview:self.badge];
// Default design initialization
self.badgeBGColor = [UIColor redColor];
self.badgeTextColor = [UIColor whiteColor];
self.badgeFont = [UIFont systemFontOfSize:12.0];
self.badgePadding = 6;
self.badgeMinSize = 8;
self.badgeOriginX = defaultOriginX;
self.badgeOriginY = -4;
self.shouldHideBadgeAtZero = YES;
self.shouldAnimateBadge = YES;
}
#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;
if (!self.badgeValue || [self.badgeValue isEqualToString:@""] || ([self.badgeValue isEqualToString:@"0"] && self.shouldHideBadgeAtZero)) {
self.badge.hidden = YES;
} else {
self.badge.hidden = NO;
[self updateBadgeValueAnimated:YES];
}
}
- (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 {
UILabel* lbl = objc_getAssociatedObject(self, &UIBarButtonItem_badgeKey);
if(lbl==nil) {
lbl = [[UILabel alloc] initWithFrame:CGRectMake(self.badgeOriginX, self.badgeOriginY, 20, 20)];
[self setBadge:lbl];
[self badgeInit];
[self.customView addSubview:lbl];
lbl.textAlignment = NSTextAlignmentCenter;
}
return lbl;
}
-(void)setBadge:(UILabel *)badgeLabel
{
objc_setAssociatedObject(self, &UIBarButtonItem_badgeKey, badgeLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
// Badge value to be display
-(NSString *)badgeValue {
return objc_getAssociatedObject(self, &UIBarButtonItem_badgeValueKey);
}
-(void) setBadgeValue:(NSString *)badgeValue
{
objc_setAssociatedObject(self, &UIBarButtonItem_badgeValueKey, badgeValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// When changing the badge value check if we need to remove the badge
[self updateBadgeValueAnimated:YES];
[self refreshBadge];
}
// Badge background color
-(UIColor *)badgeBGColor {
return objc_getAssociatedObject(self, &UIBarButtonItem_badgeBGColorKey);
}
-(void)setBadgeBGColor:(UIColor *)badgeBGColor
{
objc_setAssociatedObject(self, &UIBarButtonItem_badgeBGColorKey, badgeBGColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
// Badge text color
-(UIColor *)badgeTextColor {
return objc_getAssociatedObject(self, &UIBarButtonItem_badgeTextColorKey);
}
-(void)setBadgeTextColor:(UIColor *)badgeTextColor
{
objc_setAssociatedObject(self, &UIBarButtonItem_badgeTextColorKey, badgeTextColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
// Badge font
-(UIFont *)badgeFont {
return objc_getAssociatedObject(self, &UIBarButtonItem_badgeFontKey);
}
-(void)setBadgeFont:(UIFont *)badgeFont
{
objc_setAssociatedObject(self, &UIBarButtonItem_badgeFontKey, badgeFont, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self refreshBadge];
}
}
// Padding value for the badge
-(CGFloat) badgePadding {
NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgePaddingKey);
return number.floatValue;
}
-(void) setBadgePadding:(CGFloat)badgePadding
{
NSNumber *number = [NSNumber numberWithDouble:badgePadding];
objc_setAssociatedObject(self, &UIBarButtonItem_badgePaddingKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self updateBadgeFrame];
}
}
// Minimum size badge to small
-(CGFloat) badgeMinSize {
NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeMinSizeKey);
return number.floatValue;
}
-(void) setBadgeMinSize:(CGFloat)badgeMinSize
{
NSNumber *number = [NSNumber numberWithDouble:badgeMinSize];
objc_setAssociatedObject(self, &UIBarButtonItem_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, &UIBarButtonItem_badgeOriginXKey);
return number.floatValue;
}
-(void) setBadgeOriginX:(CGFloat)badgeOriginX
{
NSNumber *number = [NSNumber numberWithDouble:badgeOriginX];
objc_setAssociatedObject(self, &UIBarButtonItem_badgeOriginXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (self.badge) {
[self updateBadgeFrame];
}
}
-(CGFloat) badgeOriginY {
NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeOriginYKey);
return number.floatValue;
}
-(void) setBadgeOriginY:(CGFloat)badgeOriginY
{
NSNumber *number = [NSNumber numberWithDouble:badgeOriginY];
objc_setAssociatedObject(self, &UIBarButtonItem_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, &UIBarButtonItem_shouldHideBadgeAtZeroKey);
return number.boolValue;
}
- (void)setShouldHideBadgeAtZero:(BOOL)shouldHideBadgeAtZero
{
NSNumber *number = [NSNumber numberWithBool:shouldHideBadgeAtZero];
objc_setAssociatedObject(self, &UIBarButtonItem_shouldHideBadgeAtZeroKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if(self.badge) {
[self refreshBadge];
}
}
// Badge has a bounce animation when value changes
-(BOOL) shouldAnimateBadge {
NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_shouldAnimateBadgeKey);
return number.boolValue;
}
- (void)setShouldAnimateBadge:(BOOL)shouldAnimateBadge
{
NSNumber *number = [NSNumber numberWithBool:shouldAnimateBadge];
objc_setAssociatedObject(self, &UIBarButtonItem_shouldAnimateBadgeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if(self.badge) {
[self refreshBadge];
}
}
@end