UIBarButtonItem+Badge.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. //
  2. // UIBarButtonItem+Badge.m
  3. // therichest
  4. //
  5. // Created by Mike on 2014-05-05.
  6. // Copyright (c) 2014 Valnet Inc. All rights reserved.
  7. //
  8. // https://github.com/mikeMTOL/UIBarButtonItem-Badge
  9. //
  10. // SPDX-FileCopyrightText: 2014 Mike, Valnet Inc.
  11. // SPDX-License-Identifier: MIT
  12. //
  13. #import <objc/runtime.h>
  14. #import "UIBarButtonItem+Badge.h"
  15. NSString const *UIBarButtonItem_badgeKey = @"UIBarButtonItem_badgeKey";
  16. NSString const *UIBarButtonItem_badgeBGColorKey = @"UIBarButtonItem_badgeBGColorKey";
  17. NSString const *UIBarButtonItem_badgeTextColorKey = @"UIBarButtonItem_badgeTextColorKey";
  18. NSString const *UIBarButtonItem_badgeFontKey = @"UIBarButtonItem_badgeFontKey";
  19. NSString const *UIBarButtonItem_badgePaddingKey = @"UIBarButtonItem_badgePaddingKey";
  20. NSString const *UIBarButtonItem_badgeMinSizeKey = @"UIBarButtonItem_badgeMinSizeKey";
  21. NSString const *UIBarButtonItem_badgeOriginXKey = @"UIBarButtonItem_badgeOriginXKey";
  22. NSString const *UIBarButtonItem_badgeOriginYKey = @"UIBarButtonItem_badgeOriginYKey";
  23. NSString const *UIBarButtonItem_shouldHideBadgeAtZeroKey = @"UIBarButtonItem_shouldHideBadgeAtZeroKey";
  24. NSString const *UIBarButtonItem_shouldAnimateBadgeKey = @"UIBarButtonItem_shouldAnimateBadgeKey";
  25. NSString const *UIBarButtonItem_badgeValueKey = @"UIBarButtonItem_badgeValueKey";
  26. @implementation UIBarButtonItem (Badge)
  27. @dynamic badgeValue, badgeBGColor, badgeTextColor, badgeFont;
  28. @dynamic badgePadding, badgeMinSize, badgeOriginX, badgeOriginY;
  29. @dynamic shouldHideBadgeAtZero, shouldAnimateBadge;
  30. - (void)badgeInit
  31. {
  32. UIView *superview = nil;
  33. CGFloat defaultOriginX = 0;
  34. if (self.customView) {
  35. superview = self.customView;
  36. defaultOriginX = superview.frame.size.width - self.badge.frame.size.width/2;
  37. // Avoids badge to be clipped when animating its scale
  38. superview.clipsToBounds = NO;
  39. } else if ([self respondsToSelector:@selector(view)] && [(id)self view]) {
  40. superview = [(id)self view];
  41. defaultOriginX = superview.frame.size.width - self.badge.frame.size.width;
  42. }
  43. [superview addSubview:self.badge];
  44. // Default design initialization
  45. self.badgeBGColor = [UIColor redColor];
  46. self.badgeTextColor = [UIColor whiteColor];
  47. self.badgeFont = [UIFont systemFontOfSize:12.0];
  48. self.badgePadding = 6;
  49. self.badgeMinSize = 8;
  50. self.badgeOriginX = defaultOriginX;
  51. self.badgeOriginY = -4;
  52. self.shouldHideBadgeAtZero = YES;
  53. self.shouldAnimateBadge = YES;
  54. }
  55. #pragma mark - Utility methods
  56. // Handle badge display when its properties have been changed (color, font, ...)
  57. - (void)refreshBadge
  58. {
  59. // Change new attributes
  60. self.badge.textColor = self.badgeTextColor;
  61. self.badge.backgroundColor = self.badgeBGColor;
  62. self.badge.font = self.badgeFont;
  63. if (!self.badgeValue || [self.badgeValue isEqualToString:@""] || ([self.badgeValue isEqualToString:@"0"] && self.shouldHideBadgeAtZero)) {
  64. self.badge.hidden = YES;
  65. } else {
  66. self.badge.hidden = NO;
  67. [self updateBadgeValueAnimated:YES];
  68. }
  69. }
  70. - (CGSize) badgeExpectedSize
  71. {
  72. // When the value changes the badge could need to get bigger
  73. // Calculate expected size to fit new value
  74. // Use an intermediate label to get expected size thanks to sizeToFit
  75. // We don't call sizeToFit on the true label to avoid bad display
  76. UILabel *frameLabel = [self duplicateLabel:self.badge];
  77. [frameLabel sizeToFit];
  78. CGSize expectedLabelSize = frameLabel.frame.size;
  79. return expectedLabelSize;
  80. }
  81. - (void)updateBadgeFrame
  82. {
  83. CGSize expectedLabelSize = [self badgeExpectedSize];
  84. // Make sure that for small value, the badge will be big enough
  85. CGFloat minHeight = expectedLabelSize.height;
  86. // Using a const we make sure the badge respect the minimum size
  87. minHeight = (minHeight < self.badgeMinSize) ? self.badgeMinSize : expectedLabelSize.height;
  88. CGFloat minWidth = expectedLabelSize.width;
  89. CGFloat padding = self.badgePadding;
  90. // Using const we make sure the badge doesn't get too smal
  91. minWidth = (minWidth < minHeight) ? minHeight : expectedLabelSize.width;
  92. self.badge.layer.masksToBounds = YES;
  93. self.badge.frame = CGRectMake(self.badgeOriginX, self.badgeOriginY, minWidth + padding, minHeight + padding);
  94. self.badge.layer.cornerRadius = (minHeight + padding) / 2;
  95. }
  96. // Handle the badge changing value
  97. - (void)updateBadgeValueAnimated:(BOOL)animated
  98. {
  99. // Bounce animation on badge if value changed and if animation authorized
  100. if (animated && self.shouldAnimateBadge && ![self.badge.text isEqualToString:self.badgeValue]) {
  101. CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  102. [animation setFromValue:[NSNumber numberWithFloat:1.5]];
  103. [animation setToValue:[NSNumber numberWithFloat:1]];
  104. [animation setDuration:0.2];
  105. [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.4f :1.3f :1.f :1.f]];
  106. [self.badge.layer addAnimation:animation forKey:@"bounceAnimation"];
  107. }
  108. // Set the new value
  109. self.badge.text = self.badgeValue;
  110. // Animate the size modification if needed
  111. if (animated && self.shouldAnimateBadge) {
  112. [UIView animateWithDuration:0.2 animations:^{
  113. [self updateBadgeFrame];
  114. }];
  115. } else {
  116. [self updateBadgeFrame];
  117. }
  118. }
  119. - (UILabel *)duplicateLabel:(UILabel *)labelToCopy
  120. {
  121. UILabel *duplicateLabel = [[UILabel alloc] initWithFrame:labelToCopy.frame];
  122. duplicateLabel.text = labelToCopy.text;
  123. duplicateLabel.font = labelToCopy.font;
  124. return duplicateLabel;
  125. }
  126. - (void)removeBadge
  127. {
  128. // Animate badge removal
  129. [UIView animateWithDuration:0.2 animations:^{
  130. self.badge.transform = CGAffineTransformMakeScale(0, 0);
  131. } completion:^(BOOL finished) {
  132. [self.badge removeFromSuperview];
  133. self.badge = nil;
  134. }];
  135. }
  136. #pragma mark - getters/setters
  137. -(UILabel*) badge {
  138. UILabel* lbl = objc_getAssociatedObject(self, &UIBarButtonItem_badgeKey);
  139. if(lbl==nil) {
  140. lbl = [[UILabel alloc] initWithFrame:CGRectMake(self.badgeOriginX, self.badgeOriginY, 20, 20)];
  141. [self setBadge:lbl];
  142. [self badgeInit];
  143. [self.customView addSubview:lbl];
  144. lbl.textAlignment = NSTextAlignmentCenter;
  145. }
  146. return lbl;
  147. }
  148. -(void)setBadge:(UILabel *)badgeLabel
  149. {
  150. objc_setAssociatedObject(self, &UIBarButtonItem_badgeKey, badgeLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  151. }
  152. // Badge value to be display
  153. -(NSString *)badgeValue {
  154. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeValueKey);
  155. }
  156. -(void) setBadgeValue:(NSString *)badgeValue
  157. {
  158. objc_setAssociatedObject(self, &UIBarButtonItem_badgeValueKey, badgeValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  159. // When changing the badge value check if we need to remove the badge
  160. [self updateBadgeValueAnimated:YES];
  161. [self refreshBadge];
  162. }
  163. // Badge background color
  164. -(UIColor *)badgeBGColor {
  165. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeBGColorKey);
  166. }
  167. -(void)setBadgeBGColor:(UIColor *)badgeBGColor
  168. {
  169. objc_setAssociatedObject(self, &UIBarButtonItem_badgeBGColorKey, badgeBGColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  170. if (self.badge) {
  171. [self refreshBadge];
  172. }
  173. }
  174. // Badge text color
  175. -(UIColor *)badgeTextColor {
  176. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeTextColorKey);
  177. }
  178. -(void)setBadgeTextColor:(UIColor *)badgeTextColor
  179. {
  180. objc_setAssociatedObject(self, &UIBarButtonItem_badgeTextColorKey, badgeTextColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  181. if (self.badge) {
  182. [self refreshBadge];
  183. }
  184. }
  185. // Badge font
  186. -(UIFont *)badgeFont {
  187. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeFontKey);
  188. }
  189. -(void)setBadgeFont:(UIFont *)badgeFont
  190. {
  191. objc_setAssociatedObject(self, &UIBarButtonItem_badgeFontKey, badgeFont, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  192. if (self.badge) {
  193. [self refreshBadge];
  194. }
  195. }
  196. // Padding value for the badge
  197. -(CGFloat) badgePadding {
  198. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgePaddingKey);
  199. return number.floatValue;
  200. }
  201. -(void) setBadgePadding:(CGFloat)badgePadding
  202. {
  203. NSNumber *number = [NSNumber numberWithDouble:badgePadding];
  204. objc_setAssociatedObject(self, &UIBarButtonItem_badgePaddingKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  205. if (self.badge) {
  206. [self updateBadgeFrame];
  207. }
  208. }
  209. // Minimum size badge to small
  210. -(CGFloat) badgeMinSize {
  211. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeMinSizeKey);
  212. return number.floatValue;
  213. }
  214. -(void) setBadgeMinSize:(CGFloat)badgeMinSize
  215. {
  216. NSNumber *number = [NSNumber numberWithDouble:badgeMinSize];
  217. objc_setAssociatedObject(self, &UIBarButtonItem_badgeMinSizeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  218. if (self.badge) {
  219. [self updateBadgeFrame];
  220. }
  221. }
  222. // Values for offseting the badge over the BarButtonItem you picked
  223. -(CGFloat) badgeOriginX {
  224. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeOriginXKey);
  225. return number.floatValue;
  226. }
  227. -(void) setBadgeOriginX:(CGFloat)badgeOriginX
  228. {
  229. NSNumber *number = [NSNumber numberWithDouble:badgeOriginX];
  230. objc_setAssociatedObject(self, &UIBarButtonItem_badgeOriginXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  231. if (self.badge) {
  232. [self updateBadgeFrame];
  233. }
  234. }
  235. -(CGFloat) badgeOriginY {
  236. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeOriginYKey);
  237. return number.floatValue;
  238. }
  239. -(void) setBadgeOriginY:(CGFloat)badgeOriginY
  240. {
  241. NSNumber *number = [NSNumber numberWithDouble:badgeOriginY];
  242. objc_setAssociatedObject(self, &UIBarButtonItem_badgeOriginYKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  243. if (self.badge) {
  244. [self updateBadgeFrame];
  245. }
  246. }
  247. // In case of numbers, remove the badge when reaching zero
  248. -(BOOL) shouldHideBadgeAtZero {
  249. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_shouldHideBadgeAtZeroKey);
  250. return number.boolValue;
  251. }
  252. - (void)setShouldHideBadgeAtZero:(BOOL)shouldHideBadgeAtZero
  253. {
  254. NSNumber *number = [NSNumber numberWithBool:shouldHideBadgeAtZero];
  255. objc_setAssociatedObject(self, &UIBarButtonItem_shouldHideBadgeAtZeroKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  256. if(self.badge) {
  257. [self refreshBadge];
  258. }
  259. }
  260. // Badge has a bounce animation when value changes
  261. -(BOOL) shouldAnimateBadge {
  262. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_shouldAnimateBadgeKey);
  263. return number.boolValue;
  264. }
  265. - (void)setShouldAnimateBadge:(BOOL)shouldAnimateBadge
  266. {
  267. NSNumber *number = [NSNumber numberWithBool:shouldAnimateBadge];
  268. objc_setAssociatedObject(self, &UIBarButtonItem_shouldAnimateBadgeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  269. if(self.badge) {
  270. [self refreshBadge];
  271. }
  272. }
  273. @end