diff --git a/Classes/StandardTabItemsExampleController.m b/Classes/StandardTabItemsExampleController.m index 7f76dbc..cde038d 100644 --- a/Classes/StandardTabItemsExampleController.m +++ b/Classes/StandardTabItemsExampleController.m @@ -52,4 +52,8 @@ - (void)tabView:(RKTabView *)tabView tabBecameDisabledAtIndex:(NSUInteger)index NSLog(@"Tab № %tu became disabled on tab view", index); } +- (void)tabView:(RKTabView *)tabView didTapOnEnabledItemAtIndex:(NSUInteger)index tab:(RKTabItem *)tabItem { + NSLog(@"Tab № %tu already enabled on tab view", index); +} + @end diff --git a/RKTabView.podspec b/RKTabView.podspec index 8c9f167..a4f06a5 100644 --- a/RKTabView.podspec +++ b/RKTabView.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.platform = :ios, '5.0' s.source = { :git => "https://github.com/RafaelKayumov/RKTabView.git", :tag => s.version } - s.source_files = 'RKTabView/RKTab{Item,View}.{h,m}' + s.source_files = 'RKTabView/RKTab{Item,View}.{h,m}', 'RKTabView/UIView+Badge.{h,m}' s.requires_arc = true end diff --git a/RKTabView.xcodeproj/project.pbxproj b/RKTabView.xcodeproj/project.pbxproj index 4dd0360..6be3160 100644 --- a/RKTabView.xcodeproj/project.pbxproj +++ b/RKTabView.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 3A364D9B1B84ECD80097C65E /* UIView+Badge.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A364D9A1B84ECD80097C65E /* UIView+Badge.m */; }; 780B526C183BB63C0009D12A /* facebook@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 780B5268183BB63C0009D12A /* facebook@2x.png */; }; 780B526D183BB63C0009D12A /* flickr@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 780B5269183BB63C0009D12A /* flickr@2x.png */; }; 780B526E183BB63C0009D12A /* googleplus@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 780B526A183BB63C0009D12A /* googleplus@2x.png */; }; @@ -58,6 +59,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 3A364D991B84ECD80097C65E /* UIView+Badge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+Badge.h"; sourceTree = ""; }; + 3A364D9A1B84ECD80097C65E /* UIView+Badge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+Badge.m"; sourceTree = ""; }; 780B5268183BB63C0009D12A /* facebook@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "facebook@2x.png"; sourceTree = ""; }; 780B5269183BB63C0009D12A /* flickr@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "flickr@2x.png"; sourceTree = ""; }; 780B526A183BB63C0009D12A /* googleplus@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "googleplus@2x.png"; sourceTree = ""; }; @@ -232,6 +235,8 @@ 78A7C6C8183A06B60075A7DD /* RKTabItem.m */, 78A7C6C9183A06B60075A7DD /* RKTabView.h */, 78A7C6C6183A06B60075A7DD /* RKTabView.m */, + 3A364D991B84ECD80097C65E /* UIView+Badge.h */, + 3A364D9A1B84ECD80097C65E /* UIView+Badge.m */, ); path = RKTabView; sourceTree = ""; @@ -379,6 +384,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3A364D9B1B84ECD80097C65E /* UIView+Badge.m in Sources */, 780B5278183BC4D70009D12A /* StandardTabItemsExampleController.m in Sources */, 780B5299183BCB0B0009D12A /* UnexcludableTabsExampleController.m in Sources */, 78A7C6E0183A06D80075A7DD /* AppDelegate.m in Sources */, diff --git a/RKTabView/RKTabItem.h b/RKTabView/RKTabItem.h index 879c508..5f0b9bf 100644 --- a/RKTabView/RKTabItem.h +++ b/RKTabView/RKTabItem.h @@ -28,15 +28,18 @@ typedef enum { @property (nonatomic, strong) UIColor *titleFontColorEnabled; @property (nonatomic, strong, readonly) UIImage *imageForCurrentState; +@property (nonatomic, assign) NSInteger badgeValue; +@property (nonatomic, assign) CGSize badgeOffset; + + (RKTabItem *)createUsualItemWithImageEnabled:(UIImage *)imageEnabled - imageDisabled:(UIImage *)imageDisabled; + imageDisabled:(UIImage *)imageDisabled; + (RKTabItem *)createUnexcludableItemWithImageEnabled:(UIImage *)imageEnabled - imageDisabled:(UIImage *)imageDisabled; + imageDisabled:(UIImage *)imageDisabled; + (RKTabItem *)createButtonItemWithImage:(UIImage *)image - target:(id)target - selector:(SEL)selector; + target:(id)target + selector:(SEL)selector; - (void)switchState; diff --git a/RKTabView/RKTabItem.m b/RKTabView/RKTabItem.m index fffdd16..1be949e 100644 --- a/RKTabView/RKTabItem.m +++ b/RKTabView/RKTabItem.m @@ -17,38 +17,47 @@ @interface RKTabItem () @implementation RKTabItem + (RKTabItem *)createUsualItemWithImageEnabled:(UIImage *)imageEnabled - imageDisabled:(UIImage *)imageDisabled { + imageDisabled:(UIImage *)imageDisabled { RKTabItem *tabItem = [[RKTabItem alloc] init]; if (tabItem) { tabItem.imageEnabled = imageEnabled; tabItem.imageDisabled = imageDisabled; tabItem.tabState = TabStateDisabled; tabItem.tabType = TabTypeUsual; + + tabItem.badgeValue = 0; + tabItem.badgeOffset = CGSizeZero; } return tabItem; } + (RKTabItem *)createUnexcludableItemWithImageEnabled:(UIImage *)imageEnabled - imageDisabled:(UIImage *)imageDisabled { + imageDisabled:(UIImage *)imageDisabled { RKTabItem *tabItem = [[RKTabItem alloc] init]; if (tabItem) { tabItem.imageEnabled = imageEnabled; tabItem.imageDisabled = imageDisabled; tabItem.tabState = TabStateDisabled; tabItem.tabType = TabTypeUnexcludable; + + tabItem.badgeValue = 0; + tabItem.badgeOffset = CGSizeZero; } return tabItem; } + (RKTabItem *)createButtonItemWithImage:(UIImage *)image - target:(id)target - selector:(SEL)selector { + target:(id)target + selector:(SEL)selector { RKTabItem *tabItem = [[RKTabItem alloc] init]; if (tabItem) { tabItem.imageEnabled = image; tabItem.tabType = TabTypeButton; tabItem.target = target; tabItem.selector = selector; + + tabItem.badgeValue = 0; + tabItem.badgeOffset = CGSizeZero; } return tabItem; } diff --git a/RKTabView/RKTabView.h b/RKTabView/RKTabView.h index aeb95ba..3b67a28 100644 --- a/RKTabView/RKTabView.h +++ b/RKTabView/RKTabView.h @@ -22,6 +22,8 @@ static inline HorizontalEdgeInsets HorizontalEdgeInsetsMake (CGFloat left, CGFlo - (void)tabView:(RKTabView *)tabView tabBecameEnabledAtIndex:(NSUInteger)index tab:(RKTabItem *)tabItem; //Called Only for unexcludable items. (TabTypeUnexcludable) - (void)tabView:(RKTabView *)tabView tabBecameDisabledAtIndex:(NSUInteger)index tab:(RKTabItem *)tabItem; +//Called Only for usual items. (TabTypeUsual) +- (void)tabView:(RKTabView *)tabView didTapOnEnabledItemAtIndex:(NSUInteger)index tab:(RKTabItem *)tabItem; @end @@ -39,9 +41,14 @@ static inline HorizontalEdgeInsets HorizontalEdgeInsetsMake (CGFloat left, CGFlo @property (nonatomic, strong) NSArray *tabItems; @property (nonatomic, readwrite) HorizontalEdgeInsets horizontalInsets; +@property (nonatomic, strong) UIFont *badgeTitleFont; +@property (nonatomic, strong) UIColor *badgeTitleColor; +@property (nonatomic, strong) UIColor *badgeBackgroundColor; + - (id)initWithFrame:(CGRect)frame andTabItems:(NSArray *)tabItems; - (void)switchTabIndex:(NSUInteger)index; +- (void)setBadgeValue:(NSInteger)value forTabAtIndex:(NSUInteger)index; - (RKTabItem *)selectedTabItem; @end diff --git a/RKTabView/RKTabView.m b/RKTabView/RKTabView.m index 73e5b07..d907039 100644 --- a/RKTabView/RKTabView.m +++ b/RKTabView/RKTabView.m @@ -3,6 +3,7 @@ #import "RKTabView.h" #import "RkTabItem.h" +#import "UIView+Badge.h" #define DARKER_BACKGROUND_VIEW_TAG 33 @@ -37,11 +38,11 @@ - (RKTabItem *)selectedTabItem { NSUInteger selectedIndex = [self.tabItems indexOfObjectPassingTest:^BOOL(RKTabItem *obj, NSUInteger idx, BOOL *stop) { return obj.tabState == TabStateEnabled; }]; - + if (selectedIndex == NSNotFound) { return nil; } - + RKTabItem *selectedItem = [self.tabItems objectAtIndex:selectedIndex]; return selectedItem; } @@ -91,6 +92,27 @@ - (UIColor *)upperSeparatorLineColor { return _upperSeparatorLineColor; } +- (UIColor *)badgeBackgroundColor { + if (!_badgeBackgroundColor) { + _badgeBackgroundColor = [UIColor redColor]; + } + return _badgeBackgroundColor; +} + +- (UIColor *)badgeTitleColor { + if (!_badgeTitleColor) { + _badgeTitleColor = [UIColor whiteColor]; + } + return _badgeTitleColor; +} + +- (UIFont *)badgeTitleFont { + if (!_badgeTitleFont) { + _badgeTitleFont = [UIFont italicSystemFontOfSize:15]; + } + return _badgeTitleFont; +} + #pragma mark - Private - (void)cleanTabView { @@ -103,6 +125,7 @@ - (void)cleanTabView { - (void)buildUI { //clean before layout items [self cleanTabView]; + //build UI for (RKTabItem *item in self.tabItems) { UIControl *tab = [self tabForItem:item]; @@ -166,6 +189,13 @@ - (void)switchTab:(RKTabItem *)tabItem notify:(BOOL)notify { } } } + else { + if (self.delegate) { + if ([self delegateRespondsToTapOnEnabledSelector] && notify) { + [self.delegate tabView:self didTapOnEnabledItemAtIndex:[self indexOfTab:tabItem] tab:tabItem]; + } + } + } [self setTabContent:tabItem]; break; } @@ -179,6 +209,13 @@ - (void)pressedTab:(id)sender { [self switchTab:tabItem notify:YES]; } +- (void)setBadgeValue:(NSInteger)value forTabAtIndex:(NSUInteger)index { + if (index < self.tabItems.count) { + [(RKTabItem *)self.tabItems[index] setBadgeValue:value]; + [self setTabContent:self.tabItems[index]]; + } +} + #pragma mark - Helper methods - (UIControl *)existingTabForTabItem:(RKTabItem *)tabItem { @@ -217,7 +254,7 @@ - (CGRect)frameForTab:(RKTabItem *)tabItem { - (void)setTabContent:(UIControl *)tab withTabItem:(RKTabItem *)tabItem { //clean tab before setting content for (UIView *subview in tab.subviews) { - if (subview != [tab viewWithTag:DARKER_BACKGROUND_VIEW_TAG]) { + if ((subview != [tab viewWithTag:DARKER_BACKGROUND_VIEW_TAG]) && (subview != tab.badge)) { [subview removeFromSuperview]; } } @@ -230,7 +267,10 @@ - (void)setTabContent:(UIControl *)tab withTabItem:(RKTabItem *)tabItem { titleLabel.numberOfLines = 2; titleLabel.lineBreakMode = NSLineBreakByWordWrapping; titleLabel.textAlignment = NSTextAlignmentCenter; - titleLabel.adjustsLetterSpacingToFitWidth = YES; + + //This was deprecated in iOS 7. If this is needed one can use the attributedString and adjust the NSKernAttributeName there. + //titleLabel.adjustsLetterSpacingToFitWidth = YES; + titleLabel.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth; UIFont *font = nil; @@ -254,7 +294,10 @@ - (void)setTabContent:(UIControl *)tab withTabItem:(RKTabItem *)tabItem { titleLabel.textColor = textColor; titleLabel.backgroundColor = [UIColor clearColor]; - titleSize = [tabItem.titleString sizeWithFont:titleLabel.font constrainedToSize:CGSizeMake(tab.bounds.size.width, MAXFLOAT) lineBreakMode:NSLineBreakByWordWrapping]; + titleSize = [tabItem.titleString boundingRectWithSize:CGSizeMake(tab.bounds.size.width, MAXFLOAT) + options:NSStringDrawingUsesLineFragmentOrigin + attributes:@{NSFontAttributeName: titleLabel.font} + context:nil].size; titleLabel.text = tabItem.titleString; } @@ -273,8 +316,8 @@ - (void)setTabContent:(UIControl *)tab withTabItem:(RKTabItem *)tabItem { //Subviews frames if (titleLabel) { CGFloat wholeGapHeight = tab.bounds.size.height - ((UIView *)interfaceElement).bounds.size.height - titleSize.height; - titleLabel.frame = CGRectMake(tab.bounds.size.width/2 - titleSize.width/2, wholeGapHeight*2/3+((UIView *)interfaceElement).bounds.size.height, titleSize.width+2, titleSize.height+2); - ((UIView *)interfaceElement).frame = CGRectMake(tab.bounds.size.width/2 - ((UIView *)interfaceElement).bounds.size.width/2, wholeGapHeight/3, ((UIView *)interfaceElement).bounds.size.width, ((UIView *)interfaceElement).bounds.size.height); + titleLabel.frame = CGRectMake(tab.bounds.size.width/2 - titleSize.width/2, wholeGapHeight*2/4+((UIView *)interfaceElement).bounds.size.height, titleSize.width+2, titleSize.height+2); + ((UIView *)interfaceElement).frame = CGRectMake(tab.bounds.size.width/2 - ((UIView *)interfaceElement).bounds.size.width/2, wholeGapHeight/2, ((UIView *)interfaceElement).bounds.size.width, ((UIView *)interfaceElement).bounds.size.height); [tab addSubview:titleLabel]; } else { ((UIView *)interfaceElement).center = CGPointMake(tab.bounds.size.width/2, tab.bounds.size.height/2); @@ -303,6 +346,9 @@ - (void)setTabContent:(UIControl *)tab withTabItem:(RKTabItem *)tabItem { } else { tab.backgroundColor = tabItem.backgroundColor ? tabItem.backgroundColor : [UIColor clearColor]; } + + // Badge count + tab.badgeValue = [NSString stringWithFormat:@"%@", @(tabItem.badgeValue)]; } - (void)setTabContent:(RKTabItem *)tabItem { @@ -331,6 +377,16 @@ - (UIControl *)tabForItem:(RKTabItem *)tabItem { [tab addSubview:darkerBackgroundView]; } + // Set up badge view + tab.badgeBGColor = self.badgeBackgroundColor; + tab.badgeTextColor = self.badgeTitleColor; + tab.badgeFont = self.badgeTitleFont; + tab.shouldHideBadgeAtZero = YES; + tab.shouldAnimateBadge = YES; + tab.badgeValue = [NSString stringWithFormat:@"%@", @(tabItem.badgeValue)]; + tab.badgeOffsetX = tabItem.badgeOffset.width; + tab.badgeOffsetY = tabItem.badgeOffset.height; + //setup [self setTabContent:tab withTabItem:tabItem]; } @@ -355,6 +411,15 @@ - (BOOL)delegateRespondsToEnableSelector { } } +- (BOOL)delegateRespondsToTapOnEnabledSelector { + if ([self.delegate respondsToSelector:@selector(tabView:didTapOnEnabledItemAtIndex:tab:)]) { + return YES; + } else { + NSLog(@"Attention! Your delegate doesn't have tabView:didTapOnEnabledItemAtIndex:tab: method implementation!"); + return NO; + } +} + #pragma mark - Drawing - (void)drawRect:(CGRect)rect { diff --git a/RKTabView/UIView+Badge.h b/RKTabView/UIView+Badge.h new file mode 100644 index 0000000..6944045 --- /dev/null +++ b/RKTabView/UIView+Badge.h @@ -0,0 +1,43 @@ +// +// RKBadgeView.h +// RKTabView +// +// Created by Cao Tri DO on 19/08/2015. +// Copyright (c) 2015 Cao Tri DO. All rights reserved. +// + +#import + +@interface UIView (Badge) + +@property (strong, nonatomic) UILabel *badge; + +// Badge value to be display +@property (nonatomic) NSString *badgeValue; + +// Badge background color +@property (nonatomic) UIColor *badgeBGColor; + +// Badge text color +@property (nonatomic) UIColor *badgeTextColor; + +// Badge font +@property (nonatomic) UIFont *badgeFont; + +// Padding value for the badge +@property (nonatomic) CGFloat badgePadding; + +// Minimum size badge to small +@property (nonatomic) CGFloat badgeMinSize; + +// Values for offseting the badge over the BarButtonItem you picked +@property (nonatomic) CGFloat badgeOffsetX; +@property (nonatomic) CGFloat badgeOffsetY; + +// In case of numbers, remove the badge when reaching zero +@property BOOL shouldHideBadgeAtZero; + +// Badge has a bounce animation when value changes +@property BOOL shouldAnimateBadge; + +@end diff --git a/RKTabView/UIView+Badge.m b/RKTabView/UIView+Badge.m new file mode 100644 index 0000000..e338129 --- /dev/null +++ b/RKTabView/UIView+Badge.m @@ -0,0 +1,314 @@ +// +// RKBadgeView.m +// RKTabView +// +// Created by Cao Tri DO on 19/08/2015. +// Copyright (c) 2015 Cao Tri DO. All rights reserved. +// + +#import +#import "UIView+Badge.h" + +NSString const *UIView_badgeKey = @"UIView_badgeKey"; + +NSString const *UIView_badgeBGColorKey = @"UIView_badgeBGColorKey"; +NSString const *UIView_badgeTextColorKey = @"UIView_badgeTextColorKey"; +NSString const *UIView_badgeFontKey = @"UIView_badgeFontKey"; +NSString const *UIView_badgePaddingKey = @"UIView_badgePaddingKey"; +NSString const *UIView_badgeMinSizeKey = @"UIView_badgeMinSizeKey"; +NSString const *UIView_badgeOriginXKey = @"UIView_badgeOriginXKey"; +NSString const *UIView_badgeOriginYKey = @"UIView_badgeOriginYKey"; +NSString const *UIView_badgeOffsetXKey = @"UIView_badgeOffsetXKey"; +NSString const *UIView_badgeOffsetYKey = @"UIView_badgeOffsetYKey"; +NSString const *UIView_shouldHideBadgeAtZeroKey = @"UIView_shouldHideBadgeAtZeroKey"; +NSString const *UIView_shouldAnimateBadgeKey = @"UIView_shouldAnimateBadgeKey"; +NSString const *UIView_badgeValueKey = @"UIView_badgeValueKey"; + +@interface UIView (badge) + +@property (nonatomic) CGFloat badgeOriginX; +@property (nonatomic) CGFloat badgeOriginY; + +@end + +@implementation UIView (Badge) + +@dynamic badgeValue, badgeBGColor, badgeTextColor, badgeFont; +@dynamic badgePadding, badgeMinSize, badgeOffsetX, badgeOffsetY; +@dynamic shouldHideBadgeAtZero, shouldAnimateBadge; + +- (void)badgeInit { + // Default design initialization + + if (self.badgeBGColor == nil) { + self.badgeBGColor = [UIColor redColor]; + } + + if (self.badgeTextColor == nil) { + self.badgeTextColor = [UIColor whiteColor]; + } + + if (self.badgeFont == nil) { + self.badgeFont = [UIFont systemFontOfSize:12.0]; + } + + self.badgeOriginX = (self.frame.size.width - self.badge.frame.size.width/2) + self.badgeOffsetX; + self.badgeOriginY = -4 + self.badgeOffsetY; + + self.badgePadding = 4; + self.badgeMinSize = 8; + self.shouldHideBadgeAtZero = YES; + self.shouldAnimateBadge = YES; + // Avoids badge to be clipped when animating its scale + self.clipsToBounds = NO; +} + +#pragma mark - Utility methods + +- (void)refreshBadge { + self.badge.textColor = self.badgeTextColor; + self.badge.backgroundColor = self.badgeBGColor; + self.badge.font = self.badgeFont; +} + +- (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; +} + +- (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 { + return objc_getAssociatedObject(self, &UIView_badgeKey); +} + +- (void)setBadge:(UILabel *)badgeLabel { + objc_setAssociatedObject(self, &UIView_badgeKey, badgeLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (NSString *)badgeValue { + return objc_getAssociatedObject(self, &UIView_badgeValueKey); +} + +- (void)setBadgeValue:(NSString *)badgeValue { + objc_setAssociatedObject(self, &UIView_badgeValueKey, badgeValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + // When changing the badge value check if we need to remove the badge + if (!badgeValue || [badgeValue isEqualToString:@""] || ([badgeValue isEqualToString:@"0"] && self.shouldHideBadgeAtZero)) { + [self removeBadge]; + } + else if (!self.badge) { + // Create a new badge because not existing + self.badge = [[UILabel alloc] initWithFrame:CGRectMake(self.badgeOriginX, self.badgeOriginY, 20, 20)]; + [self badgeInit]; + self.badge.textColor = self.badgeTextColor; + self.badge.backgroundColor = self.badgeBGColor; + self.badge.font = self.badgeFont; + self.badge.textAlignment = NSTextAlignmentCenter; + [self addSubview:self.badge]; + [self updateBadgeValueAnimated:NO]; + } + else { + [self bringSubviewToFront:self.badge]; + [self updateBadgeValueAnimated:YES]; + } +} + +- (UIColor *)badgeBGColor { + return objc_getAssociatedObject(self, &UIView_badgeBGColorKey); +} + +- (void)setBadgeBGColor:(UIColor *)badgeBGColor { + objc_setAssociatedObject(self, &UIView_badgeBGColorKey, badgeBGColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self refreshBadge]; + } +} + +- (UIColor *)badgeTextColor { + return objc_getAssociatedObject(self, &UIView_badgeTextColorKey); +} + +- (void)setBadgeTextColor:(UIColor *)badgeTextColor { + objc_setAssociatedObject(self, &UIView_badgeTextColorKey, badgeTextColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self refreshBadge]; + } +} + +- (UIFont *)badgeFont { + return objc_getAssociatedObject(self, &UIView_badgeFontKey); +} + +- (void)setBadgeFont:(UIFont *)badgeFont { + objc_setAssociatedObject(self, &UIView_badgeFontKey, badgeFont, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self refreshBadge]; + } +} + +- (CGFloat)badgePadding { + NSNumber *number = objc_getAssociatedObject(self, &UIView_badgePaddingKey); + return number.floatValue; +} + +- (void)setBadgePadding:(CGFloat)badgePadding { + NSNumber *number = [NSNumber numberWithDouble:badgePadding]; + objc_setAssociatedObject(self, &UIView_badgePaddingKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self updateBadgeFrame]; + } +} + +- (CGFloat)badgeMinSize { + NSNumber *number = objc_getAssociatedObject(self, &UIView_badgeMinSizeKey); + return number.floatValue; +} + +- (void)setBadgeMinSize:(CGFloat)badgeMinSize { + NSNumber *number = [NSNumber numberWithDouble:badgeMinSize]; + objc_setAssociatedObject(self, &UIView_badgeMinSizeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self updateBadgeFrame]; + } +} + +- (CGFloat)badgeOriginX { + NSNumber *number = objc_getAssociatedObject(self, &UIView_badgeOriginXKey); + return number.floatValue; +} + +- (void)setBadgeOriginX:(CGFloat)badgeOriginX { + NSNumber *number = [NSNumber numberWithDouble:badgeOriginX]; + objc_setAssociatedObject(self, &UIView_badgeOriginXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self updateBadgeFrame]; + } +} + +- (CGFloat)badgeOriginY { + NSNumber *number = objc_getAssociatedObject(self, &UIView_badgeOriginYKey); + return number.floatValue; +} + +- (void)setBadgeOriginY:(CGFloat)badgeOriginY { + NSNumber *number = [NSNumber numberWithDouble:badgeOriginY]; + objc_setAssociatedObject(self, &UIView_badgeOriginYKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + [self updateBadgeFrame]; + } +} + +- (CGFloat)badgeOffsetX { + NSNumber *number = objc_getAssociatedObject(self, &UIView_badgeOffsetXKey); + return number.floatValue; +} + +- (void)setBadgeOffsetX:(CGFloat)badgeOffsetX { + NSNumber *number = [NSNumber numberWithDouble:badgeOffsetX]; + objc_setAssociatedObject(self, &UIView_badgeOffsetXKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + self.badgeOriginX = (self.frame.size.width - self.badge.frame.size.width/2) + badgeOffsetX; + [self updateBadgeFrame]; + } +} + +- (CGFloat)badgeOffsetY { + NSNumber *number = objc_getAssociatedObject(self, &UIView_badgeOffsetYKey); + return number.floatValue; +} + +- (void)setBadgeOffsetY:(CGFloat)badgeOffsetY { + NSNumber *number = [NSNumber numberWithDouble:badgeOffsetY]; + objc_setAssociatedObject(self, &UIView_badgeOffsetYKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + if (self.badge) { + self.badgeOriginY = -4 + self.badgeOffsetY; + [self updateBadgeFrame]; + } +} + +- (BOOL)shouldHideBadgeAtZero { + NSNumber *number = objc_getAssociatedObject(self, &UIView_shouldHideBadgeAtZeroKey); + return number.boolValue; +} + +- (void)setShouldHideBadgeAtZero:(BOOL)shouldHideBadgeAtZero { + NSNumber *number = [NSNumber numberWithBool:shouldHideBadgeAtZero]; + objc_setAssociatedObject(self, &UIView_shouldHideBadgeAtZeroKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL)shouldAnimateBadge { + NSNumber *number = objc_getAssociatedObject(self, &UIView_shouldAnimateBadgeKey); + return number.boolValue; +} + +- (void)setShouldAnimateBadge:(BOOL)shouldAnimateBadge { + NSNumber *number = [NSNumber numberWithBool:shouldAnimateBadge]; + objc_setAssociatedObject(self, &UIView_shouldAnimateBadgeKey, number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end