// // UIBarButtonItem+RQExtension.m // RQCommon // // Created by 张嵘 on 2018/11/16. // Copyright © 2018 张嵘. All rights reserved. // #import "UIBarButtonItem+RQExtension.h" #import 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"; @implementation UIBarButtonItem (RQExtension) @dynamic badgeValue, badgeBGColor, badgeTextColor, badgeFont; @dynamic badgePadding, badgeMinSize, badgeOriginX, badgeOriginY; @dynamic shouldHideBadgeAtZero, shouldAnimateBadge; - (void)badgeInit { 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.layer.masksToBounds = YES; self.badge.frame = CGRectMake(self.badgeOriginX, self.badgeOriginY, minWidth + padding, minHeight + padding); self.badge.layer.cornerRadius = (minHeight + padding) / 2; } // 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 if (animated && self.shouldAnimateBadge) { [UIView animateWithDuration:0.2 animations:^{ [self updateBadgeFrame]; }]; } else { [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]; } } + (UIBarButtonItem *)rq_systemItemWithTitle:(NSString *)title titleColor:(UIColor *)titleColor imageName:(NSString *)imageName target:(id)target selector:(SEL)selector textType:(BOOL)textType { UIBarButtonItem *item = textType ? ({ /// 设置普通状态的文字属性 item = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStylePlain target:target action:selector]; titleColor = titleColor?titleColor:[UIColor whiteColor]; NSMutableDictionary *textAttrs = [NSMutableDictionary dictionary]; textAttrs[NSForegroundColorAttributeName] = titleColor; textAttrs[NSFontAttributeName] = RQRegularFont(16.0f); NSShadow *shadow = [[NSShadow alloc] init]; shadow.shadowOffset = CGSizeZero; textAttrs[NSShadowAttributeName] = shadow; [item setTitleTextAttributes:textAttrs forState:UIControlStateNormal]; // 设置高亮状态的文字属性 NSMutableDictionary *highTextAttrs = [NSMutableDictionary dictionaryWithDictionary:textAttrs]; highTextAttrs[NSForegroundColorAttributeName] = [titleColor colorWithAlphaComponent:.5f]; [item setTitleTextAttributes:highTextAttrs forState:UIControlStateHighlighted]; // 设置不可用状态(disable)的文字属性 NSMutableDictionary *disableTextAttrs = [NSMutableDictionary dictionaryWithDictionary:textAttrs]; disableTextAttrs[NSForegroundColorAttributeName] = [titleColor colorWithAlphaComponent:.5f]; [item setTitleTextAttributes:disableTextAttrs forState:UIControlStateDisabled]; item; }) : ({ item = [[UIBarButtonItem alloc] initWithImage:[[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] style:UIBarButtonItemStylePlain target:target action:selector]; item; }); return item; } + (UIBarButtonItem *)rq_customItemWithTitle:(NSString *)title font:(UIFont *)font titleColor:(UIColor *)titleColor imageName:(NSString *)imageName target:(id)target selector:(SEL)selector contentHorizontalAlignment:(UIControlContentHorizontalAlignment)contentHorizontalAlignment EdgeInsetsStyle:(RQButtonEdgeInsetsStyle)edgeInsetsStyle space:(CGFloat)space { UIButton *item = [[UIButton alloc] init]; titleColor = titleColor?titleColor:[UIColor whiteColor]; [item setTitle:title forState:UIControlStateNormal]; [item.titleLabel setFont:font]; [item setTitleColor:titleColor forState:UIControlStateNormal]; [item setTitleColor:[titleColor colorWithAlphaComponent:.5f] forState:UIControlStateHighlighted]; [item setTitleColor:[titleColor colorWithAlphaComponent:.5f] forState:UIControlStateDisabled]; [item sizeToFit]; if (item.width < 40) { item.width = 40; } item.contentHorizontalAlignment = contentHorizontalAlignment; [item addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside]; BOOL isURL = [NSString rq_isValidURL:imageName]; if (isURL) { [item sd_setImageWithURL:[NSURL URLWithString:imageName] forState:UIControlStateNormal placeholderImage:RQWebAvatarImagePlaceholder() completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if(image) { UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; image = [image scaledToSize:CGSizeMake(30, 30)]; image = [image imageByRoundCornerRadius:15]; imageView.image = image; [item setImage:imageView.image forState:UIControlStateNormal]; [item layoutButtonWithEdgeInsetsStyle:edgeInsetsStyle imageTitleSpace:space]; } }]; } else { [item setImage:[UIImage rq_imageAlwaysShowOriginalImageWithImageName:imageName] forState:UIControlStateNormal]; [item layoutButtonWithEdgeInsetsStyle:edgeInsetsStyle imageTitleSpace:space]; } return [[UIBarButtonItem alloc] initWithCustomView:item]; } /// 返回按钮 带箭头的 + (UIBarButtonItem *)rq_backItemWithTitle:(NSString *)title imageName:(NSString *)imageName target:(id)target action:(SEL)action { return [self rq_customItemWithTitle:title font:RQRegularFont_13 titleColor:nil imageName:imageName target:target selector:action contentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft EdgeInsetsStyle:RQButtonEdgeInsetsStyleLeft space:0]; } /// 返回按钮 带箭头的 + (UIBarButtonItem *)rq_backItemWithImageName:(NSString *)imageName target:(id)target action:(SEL)action { return [self rq_customItemWithTitle:@"" font:RQRegularFont_13 titleColor:nil imageName:imageName target:target selector:action contentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft EdgeInsetsStyle:RQButtonEdgeInsetsStyleLeft space:0]; } @end