UIBarButtonItem+RQExtension.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. //
  2. // UIBarButtonItem+RQExtension.m
  3. // RQCommon
  4. //
  5. // Created by 张嵘 on 2018/11/16.
  6. // Copyright © 2018 张嵘. All rights reserved.
  7. //
  8. #import "UIBarButtonItem+RQExtension.h"
  9. #import <objc/runtime.h>
  10. NSString const *UIBarButtonItem_badgeKey = @"UIBarButtonItem_badgeKey";
  11. NSString const *UIBarButtonItem_badgeBGColorKey = @"UIBarButtonItem_badgeBGColorKey";
  12. NSString const *UIBarButtonItem_badgeTextColorKey = @"UIBarButtonItem_badgeTextColorKey";
  13. NSString const *UIBarButtonItem_badgeFontKey = @"UIBarButtonItem_badgeFontKey";
  14. NSString const *UIBarButtonItem_badgePaddingKey = @"UIBarButtonItem_badgePaddingKey";
  15. NSString const *UIBarButtonItem_badgeMinSizeKey = @"UIBarButtonItem_badgeMinSizeKey";
  16. NSString const *UIBarButtonItem_badgeOriginXKey = @"UIBarButtonItem_badgeOriginXKey";
  17. NSString const *UIBarButtonItem_badgeOriginYKey = @"UIBarButtonItem_badgeOriginYKey";
  18. NSString const *UIBarButtonItem_shouldHideBadgeAtZeroKey = @"UIBarButtonItem_shouldHideBadgeAtZeroKey";
  19. NSString const *UIBarButtonItem_shouldAnimateBadgeKey = @"UIBarButtonItem_shouldAnimateBadgeKey";
  20. NSString const *UIBarButtonItem_badgeValueKey = @"UIBarButtonItem_badgeValueKey";
  21. @implementation UIBarButtonItem (RQExtension)
  22. @dynamic badgeValue, badgeBGColor, badgeTextColor, badgeFont;
  23. @dynamic badgePadding, badgeMinSize, badgeOriginX, badgeOriginY;
  24. @dynamic shouldHideBadgeAtZero, shouldAnimateBadge;
  25. - (void)badgeInit {
  26. UIView *superview = nil;
  27. CGFloat defaultOriginX = 0;
  28. if (self.customView) {
  29. superview = self.customView;
  30. defaultOriginX = superview.frame.size.width - self.badge.frame.size.width/2;
  31. // Avoids badge to be clipped when animating its scale
  32. superview.clipsToBounds = NO;
  33. } else if ([self respondsToSelector:@selector(view)] && [(id)self view]) {
  34. superview = [(id)self view];
  35. defaultOriginX = superview.frame.size.width - self.badge.frame.size.width;
  36. }
  37. [superview addSubview:self.badge];
  38. // Default design initialization
  39. self.badgeBGColor = [UIColor redColor];
  40. self.badgeTextColor = [UIColor whiteColor];
  41. self.badgeFont = [UIFont systemFontOfSize:12.0];
  42. self.badgePadding = 6;
  43. self.badgeMinSize = 8;
  44. self.badgeOriginX = defaultOriginX;
  45. self.badgeOriginY = -4;
  46. self.shouldHideBadgeAtZero = YES;
  47. self.shouldAnimateBadge = YES;
  48. }
  49. #pragma mark - Utility methods
  50. // Handle badge display when its properties have been changed (color, font, ...)
  51. - (void)refreshBadge {
  52. // Change new attributes
  53. self.badge.textColor = self.badgeTextColor;
  54. self.badge.backgroundColor = self.badgeBGColor;
  55. self.badge.font = self.badgeFont;
  56. if (!self.badgeValue || [self.badgeValue isEqualToString:@""] || ([self.badgeValue isEqualToString:@"0"] && self.shouldHideBadgeAtZero)) {
  57. self.badge.hidden = YES;
  58. } else {
  59. self.badge.hidden = NO;
  60. [self updateBadgeValueAnimated:YES];
  61. }
  62. }
  63. - (CGSize) badgeExpectedSize {
  64. // When the value changes the badge could need to get bigger
  65. // Calculate expected size to fit new value
  66. // Use an intermediate label to get expected size thanks to sizeToFit
  67. // We don't call sizeToFit on the true label to avoid bad display
  68. UILabel *frameLabel = [self duplicateLabel:self.badge];
  69. [frameLabel sizeToFit];
  70. CGSize expectedLabelSize = frameLabel.frame.size;
  71. return expectedLabelSize;
  72. }
  73. - (void)updateBadgeFrame {
  74. CGSize expectedLabelSize = [self badgeExpectedSize];
  75. // Make sure that for small value, the badge will be big enough
  76. CGFloat minHeight = expectedLabelSize.height;
  77. // Using a const we make sure the badge respect the minimum size
  78. minHeight = (minHeight < self.badgeMinSize) ? self.badgeMinSize : expectedLabelSize.height;
  79. CGFloat minWidth = expectedLabelSize.width;
  80. CGFloat padding = self.badgePadding;
  81. // Using const we make sure the badge doesn't get too smal
  82. minWidth = (minWidth < minHeight) ? minHeight : expectedLabelSize.width;
  83. self.badge.layer.masksToBounds = YES;
  84. self.badge.frame = CGRectMake(self.badgeOriginX, self.badgeOriginY, minWidth + padding, minHeight + padding);
  85. self.badge.layer.cornerRadius = (minHeight + padding) / 2;
  86. }
  87. // Handle the badge changing value
  88. - (void)updateBadgeValueAnimated:(BOOL)animated {
  89. // Bounce animation on badge if value changed and if animation authorized
  90. if (animated && self.shouldAnimateBadge && ![self.badge.text isEqualToString:self.badgeValue]) {
  91. CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
  92. [animation setFromValue:[NSNumber numberWithFloat:1.5]];
  93. [animation setToValue:[NSNumber numberWithFloat:1]];
  94. [animation setDuration:0.2];
  95. [animation setTimingFunction:[CAMediaTimingFunction functionWithControlPoints:.4f :1.3f :1.f :1.f]];
  96. [self.badge.layer addAnimation:animation forKey:@"bounceAnimation"];
  97. }
  98. // Set the new value
  99. self.badge.text = self.badgeValue;
  100. // Animate the size modification if needed
  101. if (animated && self.shouldAnimateBadge) {
  102. [UIView animateWithDuration:0.2 animations:^{
  103. [self updateBadgeFrame];
  104. }];
  105. } else {
  106. [self updateBadgeFrame];
  107. }
  108. }
  109. - (UILabel *)duplicateLabel:(UILabel *)labelToCopy {
  110. UILabel *duplicateLabel = [[UILabel alloc] initWithFrame:labelToCopy.frame];
  111. duplicateLabel.text = labelToCopy.text;
  112. duplicateLabel.font = labelToCopy.font;
  113. return duplicateLabel;
  114. }
  115. - (void)removeBadge {
  116. // Animate badge removal
  117. [UIView animateWithDuration:0.2 animations:^{
  118. self.badge.transform = CGAffineTransformMakeScale(0, 0);
  119. } completion:^(BOOL finished) {
  120. [self.badge removeFromSuperview];
  121. self.badge = nil;
  122. }];
  123. }
  124. #pragma mark - getters/setters
  125. - (UILabel*)badge {
  126. UILabel* lbl = objc_getAssociatedObject(self, &UIBarButtonItem_badgeKey);
  127. if(lbl==nil) {
  128. lbl = [[UILabel alloc] initWithFrame:CGRectMake(self.badgeOriginX, self.badgeOriginY, 20, 20)];
  129. [self setBadge:lbl];
  130. [self badgeInit];
  131. [self.customView addSubview:lbl];
  132. lbl.textAlignment = NSTextAlignmentCenter;
  133. }
  134. return lbl;
  135. }
  136. - (void)setBadge:(UILabel *)badgeLabel {
  137. objc_setAssociatedObject(self, &UIBarButtonItem_badgeKey, badgeLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  138. }
  139. // Badge value to be display
  140. - (NSString *)badgeValue {
  141. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeValueKey);
  142. }
  143. - (void) setBadgeValue:(NSString *)badgeValue {
  144. objc_setAssociatedObject(self, &UIBarButtonItem_badgeValueKey, badgeValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  145. // When changing the badge value check if we need to remove the badge
  146. [self updateBadgeValueAnimated:YES];
  147. [self refreshBadge];
  148. }
  149. // Badge background color
  150. - (UIColor *)badgeBGColor {
  151. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeBGColorKey);
  152. }
  153. - (void)setBadgeBGColor:(UIColor *)badgeBGColor {
  154. objc_setAssociatedObject(self, &UIBarButtonItem_badgeBGColorKey, badgeBGColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  155. if (self.badge) {
  156. [self refreshBadge];
  157. }
  158. }
  159. // Badge text color
  160. - (UIColor *)badgeTextColor {
  161. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeTextColorKey);
  162. }
  163. - (void)setBadgeTextColor:(UIColor *)badgeTextColor {
  164. objc_setAssociatedObject(self, &UIBarButtonItem_badgeTextColorKey, badgeTextColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  165. if (self.badge) {
  166. [self refreshBadge];
  167. }
  168. }
  169. // Badge font
  170. - (UIFont *)badgeFont {
  171. return objc_getAssociatedObject(self, &UIBarButtonItem_badgeFontKey);
  172. }
  173. - (void)setBadgeFont:(UIFont *)badgeFont {
  174. objc_setAssociatedObject(self, &UIBarButtonItem_badgeFontKey, badgeFont, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  175. if (self.badge) {
  176. [self refreshBadge];
  177. }
  178. }
  179. // Padding value for the badge
  180. - (CGFloat)badgePadding {
  181. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgePaddingKey);
  182. return number.floatValue;
  183. }
  184. - (void)setBadgePadding:(CGFloat)badgePadding {
  185. NSNumber *number = [NSNumber numberWithDouble:badgePadding];
  186. objc_setAssociatedObject(self, &UIBarButtonItem_badgePaddingKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  187. if (self.badge) {
  188. [self updateBadgeFrame];
  189. }
  190. }
  191. // Minimum size badge to small
  192. - (CGFloat)badgeMinSize {
  193. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeMinSizeKey);
  194. return number.floatValue;
  195. }
  196. - (void)setBadgeMinSize:(CGFloat)badgeMinSize {
  197. NSNumber *number = [NSNumber numberWithDouble:badgeMinSize];
  198. objc_setAssociatedObject(self, &UIBarButtonItem_badgeMinSizeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  199. if (self.badge) {
  200. [self updateBadgeFrame];
  201. }
  202. }
  203. // Values for offseting the badge over the BarButtonItem you picked
  204. - (CGFloat)badgeOriginX {
  205. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeOriginXKey);
  206. return number.floatValue;
  207. }
  208. - (void)setBadgeOriginX:(CGFloat)badgeOriginX {
  209. NSNumber *number = [NSNumber numberWithDouble:badgeOriginX];
  210. objc_setAssociatedObject(self, &UIBarButtonItem_badgeOriginXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  211. if (self.badge) {
  212. [self updateBadgeFrame];
  213. }
  214. }
  215. - (CGFloat)badgeOriginY {
  216. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_badgeOriginYKey);
  217. return number.floatValue;
  218. }
  219. - (void)setBadgeOriginY:(CGFloat)badgeOriginY {
  220. NSNumber *number = [NSNumber numberWithDouble:badgeOriginY];
  221. objc_setAssociatedObject(self, &UIBarButtonItem_badgeOriginYKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  222. if (self.badge) {
  223. [self updateBadgeFrame];
  224. }
  225. }
  226. // In case of numbers, remove the badge when reaching zero
  227. - (BOOL)shouldHideBadgeAtZero {
  228. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_shouldHideBadgeAtZeroKey);
  229. return number.boolValue;
  230. }
  231. - (void)setShouldHideBadgeAtZero:(BOOL)shouldHideBadgeAtZero {
  232. NSNumber *number = [NSNumber numberWithBool:shouldHideBadgeAtZero];
  233. objc_setAssociatedObject(self, &UIBarButtonItem_shouldHideBadgeAtZeroKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  234. if(self.badge) {
  235. [self refreshBadge];
  236. }
  237. }
  238. // Badge has a bounce animation when value changes
  239. - (BOOL) shouldAnimateBadge {
  240. NSNumber *number = objc_getAssociatedObject(self, &UIBarButtonItem_shouldAnimateBadgeKey);
  241. return number.boolValue;
  242. }
  243. - (void)setShouldAnimateBadge:(BOOL)shouldAnimateBadge {
  244. NSNumber *number = [NSNumber numberWithBool:shouldAnimateBadge];
  245. objc_setAssociatedObject(self, &UIBarButtonItem_shouldAnimateBadgeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  246. if(self.badge) {
  247. [self refreshBadge];
  248. }
  249. }
  250. + (UIBarButtonItem *)rq_systemItemWithTitle:(NSString *)title
  251. titleColor:(UIColor *)titleColor
  252. imageName:(NSString *)imageName
  253. target:(id)target
  254. selector:(SEL)selector
  255. textType:(BOOL)textType {
  256. UIBarButtonItem *item = textType ?
  257. ({
  258. /// 设置普通状态的文字属性
  259. item = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStylePlain target:target action:selector];
  260. titleColor = titleColor?titleColor:[UIColor whiteColor];
  261. NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary];
  262. textAttrs[NSForegroundColorAttributeName] = titleColor;
  263. textAttrs[NSFontAttributeName] = RQRegularFont(16.0f);
  264. NSShadow *shadow = [[NSShadow alloc] init];
  265. shadow.shadowOffset = CGSizeZero;
  266. textAttrs[NSShadowAttributeName] = shadow;
  267. [item setTitleTextAttributes:textAttrs forState:UIControlStateNormal];
  268. // 设置高亮状态的文字属性
  269. NSMutableDictionary *highTextAttrs = [NSMutableDictionary dictionaryWithDictionary:textAttrs];
  270. highTextAttrs[NSForegroundColorAttributeName] = [titleColor colorWithAlphaComponent:.5f];
  271. [item setTitleTextAttributes:highTextAttrs forState:UIControlStateHighlighted];
  272. // 设置不可用状态(disable)的文字属性
  273. NSMutableDictionary *disableTextAttrs = [NSMutableDictionary dictionaryWithDictionary:textAttrs];
  274. disableTextAttrs[NSForegroundColorAttributeName] = [titleColor colorWithAlphaComponent:.5f];
  275. [item setTitleTextAttributes:disableTextAttrs forState:UIControlStateDisabled];
  276. item;
  277. }) : ({
  278. item = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:target action:selector];
  279. item;
  280. });
  281. return item;
  282. }
  283. + (UIBarButtonItem *)rq_customItemWithTitle:(NSString *)title
  284. font:(UIFont *)font
  285. titleColor:(UIColor *)titleColor
  286. imageName:(NSString *)imageName
  287. target:(id)target
  288. selector:(SEL)selector
  289. contentHorizontalAlignment:(UIControlContentHorizontalAlignment)contentHorizontalAlignment
  290. EdgeInsetsStyle:(RQButtonEdgeInsetsStyle)edgeInsetsStyle
  291. space:(CGFloat)space {
  292. UIButton *item = [[UIButton alloc] init];
  293. titleColor = titleColor?titleColor:[UIColor whiteColor];
  294. [item setTitle:title forState:UIControlStateNormal];
  295. [item.titleLabel setFont:font];
  296. [item setTitleColor:titleColor forState:UIControlStateNormal];
  297. [item setTitleColor:[titleColor colorWithAlphaComponent:.5f] forState:UIControlStateHighlighted];
  298. [item setTitleColor:[titleColor colorWithAlphaComponent:.5f] forState:UIControlStateDisabled];
  299. [item sizeToFit];
  300. if (item.width < 40) {
  301. item.width = 40;
  302. }
  303. item.contentHorizontalAlignment = contentHorizontalAlignment;
  304. [item addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside];
  305. BOOL isURL = [NSString rq_isValidURL:imageName];
  306. if (isURL) {
  307. [item sd_setImageWithURL:[NSURL URLWithString:imageName] forState:UIControlStateNormal placeholderImage:RQWebAvatarImagePlaceholder() completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
  308. if(image) {
  309. UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
  310. image = [image scaledToSize:CGSizeMake(30, 30)];
  311. image = [image imageByRoundCornerRadius:15];
  312. imageView.image = image;
  313. [item setImage:imageView.image forState:UIControlStateNormal];
  314. [item layoutButtonWithEdgeInsetsStyle:edgeInsetsStyle imageTitleSpace:space];
  315. }
  316. }];
  317. } else {
  318. [item setImage:[UIImage rq_imageAlwaysShowOriginalImageWithImageName:imageName] forState:UIControlStateNormal];
  319. [item layoutButtonWithEdgeInsetsStyle:edgeInsetsStyle imageTitleSpace:space];
  320. }
  321. return [[UIBarButtonItem alloc] initWithCustomView:item];
  322. }
  323. /// 返回按钮 带箭头的
  324. + (UIBarButtonItem *)rq_backItemWithTitle:(NSString *)title
  325. imageName:(NSString *)imageName
  326. target:(id)target
  327. action:(SEL)action
  328. {
  329. return [self rq_customItemWithTitle:title font:RQRegularFont_13 titleColor:nil imageName:imageName target:target selector:action contentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft EdgeInsetsStyle:RQButtonEdgeInsetsStyleLeft space:0];
  330. }
  331. /// 返回按钮 带箭头的
  332. + (UIBarButtonItem *)rq_backItemWithImageName:(NSString *)imageName
  333. target:(id)target
  334. action:(SEL)action
  335. {
  336. return [self rq_customItemWithTitle:@"" font:RQRegularFont_13 titleColor:nil imageName:imageName target:target selector:action contentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft EdgeInsetsStyle:RQButtonEdgeInsetsStyleLeft space:0];
  337. }
  338. @end