MKNumberBadgeView.m 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. //
  2. // MKNumberBadgeView.m
  3. // MKNumberBadgeView
  4. //
  5. // Copyright 2009-2012 Michael F. Kamprath
  6. // michael@claireware.com
  7. //
  8. // Licensed under the Apache License, Version 2.0 (the "License");
  9. // you may not use this file except in compliance with the License.
  10. // You may obtain a copy of the License at
  11. //
  12. // http://www.apache.org/licenses/LICENSE-2.0
  13. //
  14. // Unless required by applicable law or agreed to in writing, software
  15. // distributed under the License is distributed on an "AS IS" BASIS,
  16. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. // See the License for the specific language governing permissions and
  18. // limitations under the License.
  19. //
  20. #ifndef __has_feature
  21. #define __has_feature(x) 0
  22. #endif
  23. #ifndef __has_extension
  24. #define __has_extension __has_feature // Compatibility with pre-3.0 compilers.
  25. #endif
  26. #if __has_feature(objc_arc) && __clang_major__ >= 3
  27. #error "iPhoneMK is not designed to be used with ARC. Please add '-fno-objc-arc' to the compiler flags of iPhoneMK files."
  28. #endif // __has_feature(objc_arc)
  29. #import "MKNumberBadgeView.h"
  30. @interface MKNumberBadgeView ()
  31. //
  32. // private methods
  33. //
  34. - (void)initState;
  35. - (CGPathRef)newBadgePathForTextSize:(CGSize)inSize;
  36. @end
  37. @implementation MKNumberBadgeView
  38. @synthesize value=_value;
  39. @synthesize shadow;
  40. @synthesize shadowOffset;
  41. @synthesize shadowColor;
  42. @synthesize shine;
  43. @synthesize font;
  44. @synthesize fillColor;
  45. @synthesize strokeColor;
  46. @synthesize strokeWidth;
  47. @synthesize textColor;
  48. @synthesize alignment;
  49. @dynamic badgeSize;
  50. @synthesize pad;
  51. @synthesize hideWhenZero;
  52. @synthesize iOS7;
  53. - (id)initWithFrame:(CGRect)frame
  54. {
  55. if (self = [super initWithFrame:frame])
  56. {
  57. // Initialization code
  58. [self initState];
  59. }
  60. return self;
  61. }
  62. - (id)initWithCoder:(NSCoder *)decoder
  63. {
  64. if (self = [super initWithCoder:decoder])
  65. {
  66. // Initialization code
  67. [self initState];
  68. }
  69. return self;
  70. }
  71. #pragma mark -- private methods --
  72. - (void)initState;
  73. {
  74. if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
  75. self.iOS7 = YES;
  76. self.opaque = NO;
  77. self.pad = 2;
  78. self.font = self.iOS7 ? [UIFont systemFontOfSize:16] : [UIFont boldSystemFontOfSize:16];
  79. self.shadow = YES;
  80. self.shadowOffset = CGSizeMake(0, 3);
  81. self.shadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
  82. self.shine = YES;
  83. self.alignment = NSTextAlignmentCenter;
  84. self.fillColor = [UIColor redColor];
  85. self.strokeColor = [UIColor whiteColor];
  86. self.strokeWidth = 2.0;
  87. self.textColor = [UIColor whiteColor];
  88. self.hideWhenZero = NO;
  89. self.backgroundColor = [UIColor clearColor];
  90. }
  91. - (void)dealloc
  92. {
  93. [font release];
  94. [fillColor release];
  95. [strokeColor release];
  96. [textColor release];
  97. [shadowColor release];
  98. [super dealloc];
  99. }
  100. - (void)drawRect:(CGRect)rect
  101. {
  102. CGRect viewBounds = self.bounds;
  103. CGContextRef curContext = UIGraphicsGetCurrentContext();
  104. NSString* numberString;
  105. if (self.value == 0) {
  106. numberString = @"";
  107. } else {
  108. numberString = [NSString stringWithFormat:@"%lu",(unsigned long)self.value];
  109. }
  110. CGSize numberSize = [numberString sizeWithAttributes:@{NSFontAttributeName: self.font}];
  111. CGPathRef badgePath = [self newBadgePathForTextSize:numberSize];
  112. CGRect badgeRect = CGPathGetBoundingBox(badgePath);
  113. badgeRect.origin.x = 0;
  114. badgeRect.origin.y = 0;
  115. badgeRect.size.width = ceil( badgeRect.size.width );
  116. badgeRect.size.height = ceil( badgeRect.size.height );
  117. CGContextSaveGState( curContext );
  118. CGContextSetLineWidth( curContext, self.strokeWidth );
  119. CGContextSetStrokeColorWithColor( curContext, self.strokeColor.CGColor );
  120. CGContextSetFillColorWithColor( curContext, self.fillColor.CGColor );
  121. // Line stroke straddles the path, so we need to account for the outer portion
  122. badgeRect.size.width += ceilf( self.strokeWidth / 2 );
  123. badgeRect.size.height += ceilf( self.strokeWidth / 2 );
  124. CGPoint ctm;
  125. switch (self.alignment)
  126. {
  127. default:
  128. case NSTextAlignmentCenter:
  129. ctm = CGPointMake( round((viewBounds.size.width - badgeRect.size.width)/2), round((viewBounds.size.height - badgeRect.size.height)/2) );
  130. break;
  131. case NSTextAlignmentLeft:
  132. ctm = CGPointMake( 0, round((viewBounds.size.height - badgeRect.size.height)/2) );
  133. break;
  134. case NSTextAlignmentRight:
  135. ctm = CGPointMake( (viewBounds.size.width - badgeRect.size.width), round((viewBounds.size.height - badgeRect.size.height)/2) );
  136. break;
  137. }
  138. CGContextTranslateCTM( curContext, ctm.x, ctm.y);
  139. if (self.shadow && !iOS7)
  140. {
  141. CGContextSaveGState( curContext );
  142. CGSize blurSize = self.shadowOffset;
  143. CGContextSetShadowWithColor( curContext, blurSize, 4, self.shadowColor.CGColor );
  144. CGContextBeginPath( curContext );
  145. CGContextAddPath( curContext, badgePath );
  146. CGContextClosePath( curContext );
  147. CGContextDrawPath( curContext, kCGPathFillStroke );
  148. CGContextRestoreGState(curContext);
  149. }
  150. CGContextBeginPath( curContext );
  151. CGContextAddPath( curContext, badgePath );
  152. CGContextClosePath( curContext );
  153. CGContextDrawPath( curContext, iOS7 ? kCGPathFill : kCGPathFillStroke );
  154. //
  155. // add shine to badge
  156. //
  157. if (self.shine && !iOS7)
  158. {
  159. CGContextBeginPath( curContext );
  160. CGContextAddPath( curContext, badgePath );
  161. CGContextClosePath( curContext );
  162. CGContextClip(curContext);
  163. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  164. CGFloat shinyColorGradient[8] = {1, 1, 1, 0.8, 1, 1, 1, 0};
  165. CGFloat shinyLocationGradient[2] = {0, 1};
  166. CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
  167. shinyColorGradient,
  168. shinyLocationGradient, 2);
  169. CGContextSaveGState(curContext);
  170. CGContextBeginPath(curContext);
  171. CGContextMoveToPoint(curContext, 0, 0);
  172. CGFloat shineStartY = badgeRect.size.height*0.25;
  173. CGFloat shineStopY = shineStartY + badgeRect.size.height*0.4;
  174. CGContextAddLineToPoint(curContext, 0, shineStartY);
  175. CGContextAddCurveToPoint(curContext, 0, shineStopY,
  176. badgeRect.size.width, shineStopY,
  177. badgeRect.size.width, shineStartY);
  178. CGContextAddLineToPoint(curContext, badgeRect.size.width, 0);
  179. CGContextClosePath(curContext);
  180. CGContextClip(curContext);
  181. CGContextDrawLinearGradient(curContext, gradient,
  182. CGPointMake(badgeRect.size.width / 2.0, 0),
  183. CGPointMake(badgeRect.size.width / 2.0, shineStopY),
  184. kCGGradientDrawsBeforeStartLocation);
  185. CGContextRestoreGState(curContext);
  186. CGColorSpaceRelease(colorSpace);
  187. CGGradientRelease(gradient);
  188. }
  189. CGContextRestoreGState( curContext );
  190. CGPathRelease(badgePath);
  191. CGContextSaveGState( curContext );
  192. CGContextSetFillColorWithColor( curContext, self.textColor.CGColor );
  193. CGPoint textPt = CGPointMake( ctm.x + (badgeRect.size.width/2) - (numberSize.width / 1.75) , ctm.y + (badgeRect.size.height / 2) - (numberSize.height / 1.75));
  194. [numberString drawAtPoint:textPt withAttributes:@{NSFontAttributeName : self.font, NSForegroundColorAttributeName: self.textColor}];
  195. CGContextRestoreGState( curContext );
  196. }
  197. - (CGPathRef)newBadgePathForTextSize:(CGSize)inSize
  198. {
  199. CGFloat arcRadius = ceil((inSize.height+self.pad)/2.0);
  200. CGFloat badgeWidthAdjustment = inSize.width - inSize.height/2.0;
  201. CGFloat badgeWidth = 2.0*arcRadius;
  202. if ( badgeWidthAdjustment > 0.0 )
  203. {
  204. badgeWidth += badgeWidthAdjustment;
  205. }
  206. CGMutablePathRef badgePath = CGPathCreateMutable();
  207. CGPathMoveToPoint( badgePath, NULL, arcRadius, 0 );
  208. CGPathAddArc( badgePath, NULL, arcRadius, arcRadius, arcRadius, 3.0*M_PI_2, M_PI_2, YES);
  209. CGPathAddLineToPoint( badgePath, NULL, badgeWidth-arcRadius, 2.0*arcRadius);
  210. CGPathAddArc( badgePath, NULL, badgeWidth-arcRadius, arcRadius, arcRadius, M_PI_2, 3.0*M_PI_2, YES);
  211. CGPathAddLineToPoint( badgePath, NULL, arcRadius, 0 );
  212. return badgePath;
  213. }
  214. #pragma mark -- property methods --
  215. - (void)setValue:(NSUInteger)inValue
  216. {
  217. _value = inValue;
  218. if (self.hideWhenZero == YES && _value == 0)
  219. {
  220. self.hidden = YES;
  221. }
  222. else
  223. {
  224. self.hidden = NO;
  225. }
  226. [self setNeedsDisplay];
  227. }
  228. - (CGSize)badgeSize
  229. {
  230. NSString* numberString = [NSString stringWithFormat:@"%lu",(unsigned long)self.value];
  231. CGSize numberSize = [numberString sizeWithAttributes:@{NSFontAttributeName: self.font}];
  232. CGPathRef badgePath = [self newBadgePathForTextSize:numberSize];
  233. CGRect badgeRect = CGPathGetBoundingBox(badgePath);
  234. badgeRect.origin.x = 0;
  235. badgeRect.origin.y = 0;
  236. badgeRect.size.width = ceil( badgeRect.size.width );
  237. badgeRect.size.height = ceil( badgeRect.size.height );
  238. CGPathRelease(badgePath);
  239. return badgeRect.size;
  240. }
  241. @end