| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- /*
- Copyright (c) 2013 Javier Soto.
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- #import "JSBadgeView.h"
- #import <QuartzCore/QuartzCore.h>
- #include <mach-o/dyld.h>
- #if !__has_feature(objc_arc)
- #error JSBadgeView must be compiled with ARC.
- #endif
- // Silencing some deprecation warnings if your deployment target is iOS7 that can only be fixed by using methods that
- // Are only available on iOS7.
- // Soon JSBadgeView will require iOS 7 and we'll be able to use the new methods.
- #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0
- #define JSBadgeViewSilenceDeprecatedMethodStart() _Pragma("clang diagnostic push") \
- _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")
- #define JSBadgeViewSilenceDeprecatedMethodEnd() _Pragma("clang diagnostic pop")
- #else
- #define JSBadgeViewSilenceDeprecatedMethodStart()
- #define JSBadgeViewSilenceDeprecatedMethodEnd()
- #endif
- static const CGFloat JSBadgeViewShadowRadius = 0.0f;
- static const CGFloat JSBadgeViewHeight = 15.0f;
- static const CGFloat JSBadgeViewTextSideMargin = 5.0f;
- static const CGFloat JSBadgeViewCornerRadius = 7.5f;
- // Thanks to Peter Steinberger: https://gist.github.com/steipete/6526860
- static BOOL JSBadgeViewIsUIKitFlatMode(void)
- {
- static BOOL isUIKitFlatMode = NO;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- #ifndef kCFCoreFoundationVersionNumber_iOS_7_0
- #define kCFCoreFoundationVersionNumber_iOS_7_0 847.2
- #endif
- #ifndef UIKitVersionNumber_iOS_7_0
- #define UIKitVersionNumber_iOS_7_0 0xB57
- #endif
- // We get the modern UIKit if system is running >= iOS 7 and we were linked with >= SDK 7.
- if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
- isUIKitFlatMode = (NSVersionOfLinkTimeLibrary("UIKit") >> 16) >= UIKitVersionNumber_iOS_7_0;
- }
- });
- return isUIKitFlatMode;
- }
- @implementation JSBadgeView
- + (void)applyCommonStyle
- {
- JSBadgeView *badgeViewAppearanceProxy = JSBadgeView.appearance;
- badgeViewAppearanceProxy.backgroundColor = UIColor.clearColor;
- badgeViewAppearanceProxy.badgeAlignment = JSBadgeViewAlignmentTopRight;
- badgeViewAppearanceProxy.badgeBackgroundColor = UIColor.redColor;
- badgeViewAppearanceProxy.badgeTextFont = [UIFont boldSystemFontOfSize:9];
- badgeViewAppearanceProxy.badgeTextColor = UIColor.whiteColor;
- }
- + (void)applyLegacyStyle
- {
- JSBadgeView *badgeViewAppearanceProxy = JSBadgeView.appearance;
- badgeViewAppearanceProxy.badgeOverlayColor = [UIColor colorWithWhite:1.0f alpha:0.3];
- badgeViewAppearanceProxy.badgeTextShadowColor = UIColor.clearColor;
- badgeViewAppearanceProxy.badgeShadowColor = [UIColor colorWithWhite:0.0f alpha:0.4f];
- badgeViewAppearanceProxy.badgeShadowSize = CGSizeMake(0.0f, 3.0f);
- badgeViewAppearanceProxy.badgeStrokeWidth = 2.0f;
- badgeViewAppearanceProxy.badgeStrokeColor = UIColor.whiteColor;
- }
- + (void)applyIOS7Style
- {
- JSBadgeView *badgeViewAppearanceProxy = JSBadgeView.appearance;
- badgeViewAppearanceProxy.badgeOverlayColor = UIColor.clearColor;
- badgeViewAppearanceProxy.badgeTextShadowColor = UIColor.clearColor;
- badgeViewAppearanceProxy.badgeShadowColor = UIColor.clearColor;
- badgeViewAppearanceProxy.badgeStrokeWidth = 0.0f;
- badgeViewAppearanceProxy.badgeStrokeColor = badgeViewAppearanceProxy.badgeBackgroundColor;
- }
- + (void)initialize
- {
- if (self == JSBadgeView.class)
- {
- [self applyCommonStyle];
- if (JSBadgeViewIsUIKitFlatMode())
- {
- [self applyIOS7Style];
- }
- else
- {
- [self applyLegacyStyle];
- }
- }
- }
- - (id)initWithParentView:(UIView *)parentView alignment:(JSBadgeViewAlignment)alignment
- {
- if ((self = [self initWithFrame:CGRectZero]))
- {
- self.badgeAlignment = alignment;
- [parentView addSubview:self];
- }
-
- return self;
- }
- #pragma mark - Layout
- - (CGFloat)marginToDrawInside
- {
- return self.badgeStrokeWidth * 2.0f;
- }
- - (void)layoutSubviews
- {
- [super layoutSubviews];
-
- self.userInteractionEnabled = NO;
- CGRect newFrame = self.frame;
- const CGRect superviewBounds = CGRectIsEmpty(_frameToPositionInRelationWith) ? self.superview.bounds : _frameToPositionInRelationWith;
-
- const CGFloat textWidth = [self sizeOfTextForCurrentSettings].width;
- const CGFloat marginToDrawInside = [self marginToDrawInside];
- const CGFloat viewWidth = MAX(_badgeMinWidth, textWidth + JSBadgeViewTextSideMargin + (marginToDrawInside * 2));
- const CGFloat viewHeight = JSBadgeViewHeight + (marginToDrawInside * 2);
-
- const CGFloat superviewWidth = superviewBounds.size.width;
- const CGFloat superviewHeight = superviewBounds.size.height;
-
- newFrame.size.width = MAX(viewWidth, viewHeight);
- newFrame.size.height = viewHeight;
-
- switch (self.badgeAlignment) {
- case JSBadgeViewAlignmentTopLeft:
- newFrame.origin.x = -viewWidth / 2.0f;
- newFrame.origin.y = -viewHeight / 2.0f;
- break;
- case JSBadgeViewAlignmentTopRight:
- newFrame.origin.x = superviewWidth - (viewWidth / 2.0f);
- newFrame.origin.y = -viewHeight / 2.0f;
- break;
- case JSBadgeViewAlignmentTopCenter:
- newFrame.origin.x = (superviewWidth - viewWidth) / 2.0f;
- newFrame.origin.y = -viewHeight / 2.0f;
- break;
- case JSBadgeViewAlignmentCenterLeft:
- newFrame.origin.x = -viewWidth / 2.0f;
- newFrame.origin.y = (superviewHeight - viewHeight) / 2.0f;
- break;
- case JSBadgeViewAlignmentCenterRight:
- newFrame.origin.x = superviewWidth - (viewWidth / 2.0f);
- newFrame.origin.y = (superviewHeight - viewHeight) / 2.0f;
- break;
- case JSBadgeViewAlignmentBottomLeft:
- newFrame.origin.x = -viewWidth / 2.0f;
- newFrame.origin.y = superviewHeight - (viewHeight / 2.0f);
- break;
- case JSBadgeViewAlignmentBottomRight:
- newFrame.origin.x = superviewWidth - (viewWidth / 2.0f);
- newFrame.origin.y = superviewHeight - (viewHeight / 2.0f);
- break;
- case JSBadgeViewAlignmentBottomCenter:
- newFrame.origin.x = (superviewWidth - viewWidth) / 2.0f;
- newFrame.origin.y = superviewHeight - (viewHeight / 2.0f);
- break;
- case JSBadgeViewAlignmentCenter:
- newFrame.origin.x = (superviewWidth - viewWidth) / 2.0f;
- newFrame.origin.y = (superviewHeight - viewHeight) / 2.0f;
- break;
- default:
- NSAssert(NO, @"Unimplemented JSBadgeAligment type %lul", (unsigned long)self.badgeAlignment);
- }
-
- newFrame.origin.x += _badgePositionAdjustment.x;
- newFrame.origin.y += _badgePositionAdjustment.y;
-
- // Do not set frame directly so we do not interfere with any potential transform set on the view.
- self.bounds = CGRectIntegral(CGRectMake(0, 0, CGRectGetWidth(newFrame), CGRectGetHeight(newFrame)));
- self.center = CGPointMake(ceilf(CGRectGetMidX(newFrame)), ceilf(CGRectGetMidY(newFrame)));
-
- [self setNeedsDisplay];
- }
- #pragma mark - Private
- - (CGSize)sizeOfTextForCurrentSettings
- {
- JSBadgeViewSilenceDeprecatedMethodStart();
- return [self.badgeText sizeWithFont:self.badgeTextFont];
- JSBadgeViewSilenceDeprecatedMethodEnd();
- }
- #pragma mark - Setters
- - (void)setBadgeAlignment:(JSBadgeViewAlignment)badgeAlignment
- {
- if (badgeAlignment != _badgeAlignment)
- {
- _badgeAlignment = badgeAlignment;
- [self setNeedsLayout];
- }
- }
- - (void)setBadgePositionAdjustment:(CGPoint)badgePositionAdjustment
- {
- _badgePositionAdjustment = badgePositionAdjustment;
-
- [self setNeedsLayout];
- }
- - (void)setBadgeText:(NSString *)badgeText
- {
- if (badgeText != _badgeText)
- {
- _badgeText = [badgeText copy];
-
- [self setNeedsLayout];
- }
- }
- - (void)setBadgeTextColor:(UIColor *)badgeTextColor
- {
- if (badgeTextColor != _badgeTextColor)
- {
- _badgeTextColor = badgeTextColor;
-
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeTextShadowColor:(UIColor *)badgeTextShadowColor
- {
- if (badgeTextShadowColor != _badgeTextShadowColor)
- {
- _badgeTextShadowColor = badgeTextShadowColor;
-
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeTextShadowOffset:(CGSize)badgeTextShadowOffset
- {
- _badgeTextShadowOffset = badgeTextShadowOffset;
-
- [self setNeedsDisplay];
- }
- - (void)setBadgeTextFont:(UIFont *)badgeTextFont
- {
- if (badgeTextFont != _badgeTextFont)
- {
- _badgeTextFont = badgeTextFont;
-
- [self setNeedsLayout];
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeBackgroundColor:(UIColor *)badgeBackgroundColor
- {
- if (badgeBackgroundColor != _badgeBackgroundColor)
- {
- _badgeBackgroundColor = badgeBackgroundColor;
-
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeStrokeWidth:(CGFloat)badgeStrokeWidth
- {
- if (badgeStrokeWidth != _badgeStrokeWidth)
- {
- _badgeStrokeWidth = badgeStrokeWidth;
- [self setNeedsLayout];
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeStrokeColor:(UIColor *)badgeStrokeColor
- {
- if (badgeStrokeColor != _badgeStrokeColor)
- {
- _badgeStrokeColor = badgeStrokeColor;
-
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeShadowColor:(UIColor *)badgeShadowColor
- {
- if (badgeShadowColor != _badgeShadowColor)
- {
- _badgeShadowColor = badgeShadowColor;
-
- [self setNeedsDisplay];
- }
- }
- - (void)setBadgeShadowSize:(CGSize)badgeShadowSize
- {
- if (!CGSizeEqualToSize(badgeShadowSize, _badgeShadowSize))
- {
- _badgeShadowSize = badgeShadowSize;
- [self setNeedsDisplay];
- }
- }
- #pragma mark - Drawing
- - (void)drawRect:(CGRect)rect
- {
- const BOOL anyTextToDraw = (self.badgeText.length > 0);
-
- if (anyTextToDraw)
- {
- CGContextRef ctx = UIGraphicsGetCurrentContext();
- const CGFloat marginToDrawInside = [self marginToDrawInside];
- const CGRect rectToDraw = CGRectInset(rect, marginToDrawInside, marginToDrawInside);
-
- UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:rectToDraw byRoundingCorners:(UIRectCorner)UIRectCornerAllCorners cornerRadii:CGSizeMake(JSBadgeViewCornerRadius, JSBadgeViewCornerRadius)];
-
- /* Background and shadow */
- CGContextSaveGState(ctx);
- {
- CGContextAddPath(ctx, borderPath.CGPath);
-
- CGContextSetFillColorWithColor(ctx, self.badgeBackgroundColor.CGColor);
- CGContextSetShadowWithColor(ctx, self.badgeShadowSize, JSBadgeViewShadowRadius, self.badgeShadowColor.CGColor);
-
- CGContextDrawPath(ctx, kCGPathFill);
- }
- CGContextRestoreGState(ctx);
-
- const BOOL colorForOverlayPresent = self.badgeOverlayColor && ![self.badgeOverlayColor isEqual:[UIColor clearColor]];
-
- if (colorForOverlayPresent)
- {
- /* Gradient overlay */
- CGContextSaveGState(ctx);
- {
- CGContextAddPath(ctx, borderPath.CGPath);
- CGContextClip(ctx);
-
- const CGFloat height = rectToDraw.size.height;
- const CGFloat width = rectToDraw.size.width;
-
- const CGRect rectForOverlayCircle = CGRectMake(rectToDraw.origin.x,
- rectToDraw.origin.y - ceilf(height * 0.5),
- width,
- height);
- CGContextAddEllipseInRect(ctx, rectForOverlayCircle);
- CGContextSetFillColorWithColor(ctx, self.badgeOverlayColor.CGColor);
- CGContextDrawPath(ctx, kCGPathFill);
- }
- CGContextRestoreGState(ctx);
- }
-
- /* Stroke */
- CGContextSaveGState(ctx);
- {
- CGContextAddPath(ctx, borderPath.CGPath);
-
- CGContextSetLineWidth(ctx, self.badgeStrokeWidth);
- CGContextSetStrokeColorWithColor(ctx, self.badgeStrokeColor.CGColor);
-
- CGContextDrawPath(ctx, kCGPathStroke);
- }
- CGContextRestoreGState(ctx);
-
- /* Text */
- CGContextSaveGState(ctx);
- {
- CGContextSetFillColorWithColor(ctx, self.badgeTextColor.CGColor);
- CGContextSetShadowWithColor(ctx, self.badgeTextShadowOffset, 1.0, self.badgeTextShadowColor.CGColor);
-
- CGRect textFrame = rectToDraw;
- const CGSize textSize = [self sizeOfTextForCurrentSettings];
-
- textFrame.size.height = textSize.height;
- textFrame.origin.y = rectToDraw.origin.y + floorf((rectToDraw.size.height - textFrame.size.height) / 2.0f);
- JSBadgeViewSilenceDeprecatedMethodStart();
- [self.badgeText drawInRect:textFrame
- withFont:self.badgeTextFont
- lineBreakMode:NSLineBreakByClipping
- alignment:NSTextAlignmentCenter];
- JSBadgeViewSilenceDeprecatedMethodEnd();
- }
- CGContextRestoreGState(ctx);
- }
- }
- @end
|