// // UIBarButtonItem+Badge.m // therichest // // Created by Mike on 2014-05-05. // Copyright (c) 2014 Valnet Inc. All rights reserved. // #import #import "UIBarButtonItem+Badge.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"; @implementation UIBarButtonItem (Badge) @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]; } } @end