WMGaugeView.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. /*
  2. * WMGaugeView.h
  3. *
  4. * Copyright (C) 2014 William Markezana <william.markezana@me.com>
  5. *
  6. */
  7. #import "WMGaugeView.h"
  8. /* Scale conversion macro from [0-1] range to view real size range */
  9. #define FULL_SCALE(x,y) (x)*self.bounds.size.width, (y)*self.bounds.size.height
  10. @implementation WMGaugeView
  11. {
  12. /* Drawing rects */
  13. CGRect fullRect;
  14. CGRect innerRimRect;
  15. CGRect innerRimBorderRect;
  16. CGRect faceRect;
  17. CGRect rangeLabelsRect;
  18. CGRect scaleRect;
  19. /* View center */
  20. CGPoint center;
  21. /* Scale variables */
  22. CGFloat scaleRotation;
  23. CGFloat divisionValue;
  24. CGFloat subdivisionValue;
  25. CGFloat subdivisionAngle;
  26. /* Background image */
  27. UIImage *background;
  28. /* Needle layer */
  29. CALayer *rootNeedleLayer;
  30. /* Annimation completion */
  31. void (^animationCompletion)(BOOL);
  32. }
  33. #pragma mark - Initialization
  34. - (id)initWithFrame:(CGRect)frame
  35. {
  36. self = [super initWithFrame:frame];
  37. if (self)
  38. {
  39. [self initialize];
  40. }
  41. return self;
  42. }
  43. - (void)awakeFromNib
  44. {
  45. [super awakeFromNib];
  46. [self initialize];
  47. }
  48. /**
  49. * Set all properties to default values
  50. * Set all private variables to default values
  51. */
  52. - (void)initialize;
  53. {
  54. _style = nil;
  55. _showInnerRim = NO;
  56. _showInnerBackground = YES;
  57. _innerRimWidth = 0.05;
  58. _innerRimBorderWidth = 0.005;
  59. _scalePosition = 0.025;
  60. _scaleStartAngle = 30.0;
  61. _scaleEndAngle = 330.0;
  62. _scaleDivisions = 12.0;
  63. _scaleSubdivisions = 10.0;
  64. _showScale = YES;
  65. _showScaleShadow = YES;
  66. _showScaleValues = YES;
  67. _scalesubdivisionsAligment = WMGaugeViewSubdivisionsAlignmentTop;
  68. _scaleDivisionsLength = 0.045;
  69. _scaleDivisionsWidth = 0.01;
  70. _scaleSubdivisionsLength = 0.015;
  71. _scaleSubdivisionsWidth = 0.01;
  72. _value = 0.0;
  73. _minValue = 0.0;
  74. _maxValue = 240.0;
  75. background = nil;
  76. _showRangeLabels = NO;
  77. _rangeLabelsWidth = 0.05;
  78. _rangeLabelsFont = [UIFont fontWithName:@"Helvetica" size:0.05];
  79. _adjustRangeLabelSizeToFitWidth = NO;
  80. _rangeLabelsFontColor = [UIColor whiteColor];
  81. _rangeLabelsFontKerning = 1.0;
  82. _rangeValues = nil;
  83. _rangeColors = nil;
  84. _rangeLabels = nil;
  85. _scaleDivisionColor = RGB(68, 84, 105);
  86. _scaleSubDivisionColor = RGB(217, 217, 217);
  87. _scaleFont = nil;
  88. _unitOfMeasurementVerticalOffset = 0.6;
  89. _unitOfMeasurementColor = [UIColor whiteColor];
  90. _unitOfMeasurementFont = [UIFont fontWithName:@"Helvetica" size:0.04];
  91. _unitOfMeasurement = @"";
  92. _showUnitOfMeasurement = NO;
  93. animationCompletion = nil;
  94. [self initDrawingRects];
  95. [self initScale];
  96. }
  97. /**
  98. * Initialize all drawing rects and center point
  99. */
  100. - (void)initDrawingRects
  101. {
  102. center = CGPointMake(0.5, 0.5);
  103. fullRect = CGRectMake(0.0, 0.0, 1.0, 1.0);
  104. _innerRimBorderWidth = _showInnerRim ? _innerRimBorderWidth : 0.0;
  105. _innerRimWidth = _showInnerRim ? _innerRimWidth : 0.0;
  106. innerRimRect = fullRect;
  107. innerRimBorderRect = CGRectMake(innerRimRect.origin.x + _innerRimBorderWidth,
  108. innerRimRect.origin.y + _innerRimBorderWidth,
  109. innerRimRect.size.width - 2 * _innerRimBorderWidth,
  110. innerRimRect.size.height - 2 * _innerRimBorderWidth);
  111. faceRect = CGRectMake(innerRimRect.origin.x + _innerRimWidth,
  112. innerRimRect.origin.y + _innerRimWidth,
  113. innerRimRect.size.width - 2 * _innerRimWidth,
  114. innerRimRect.size.height - 2 * _innerRimWidth);
  115. rangeLabelsRect = CGRectMake(faceRect.origin.x + (_showRangeLabels ? _rangeLabelsWidth : 0.0),
  116. faceRect.origin.y + (_showRangeLabels ? _rangeLabelsWidth : 0.0),
  117. faceRect.size.width - 2 * (_showRangeLabels ? _rangeLabelsWidth : 0.0),
  118. faceRect.size.height - 2 * (_showRangeLabels ? _rangeLabelsWidth : 0.0));
  119. scaleRect = CGRectMake(rangeLabelsRect.origin.x + _scalePosition,
  120. rangeLabelsRect.origin.y + _scalePosition,
  121. rangeLabelsRect.size.width - 2 * _scalePosition,
  122. rangeLabelsRect.size.height - 2 * _scalePosition);
  123. }
  124. #pragma mark - Drawing
  125. /**
  126. * Main drawing entry point
  127. */
  128. - (void)drawRect:(CGRect)rect
  129. {
  130. if (background == nil)
  131. {
  132. // Create image context
  133. UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
  134. CGContextRef context = UIGraphicsGetCurrentContext();
  135. // Scale context for [0-1] drawings
  136. CGContextScaleCTM(context, rect.size.width , rect.size.height);
  137. // Draw gauge background in image context
  138. [self drawGauge:context];
  139. // Save background
  140. background = UIGraphicsGetImageFromCurrentImageContext();
  141. UIGraphicsEndImageContext();
  142. }
  143. // Drawing background in view
  144. [background drawInRect:rect];
  145. if (rootNeedleLayer == nil)
  146. {
  147. // Initialize needle layer
  148. rootNeedleLayer = [CALayer new];
  149. // For performance puporse, the needle layer is not scaled to [0-1] range
  150. rootNeedleLayer.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height*2);
  151. [self.layer addSublayer:rootNeedleLayer];
  152. // Draw needle
  153. [self drawNeedle];
  154. // Set needle current value
  155. [self setValue:_value animated:NO];
  156. }
  157. }
  158. /**
  159. * Gauge background drawing
  160. */
  161. - (void)drawGauge:(CGContextRef)context
  162. {
  163. [self drawRim:context];
  164. if (_showInnerBackground)
  165. [self drawFace:context];
  166. if (_showUnitOfMeasurement)
  167. [self drawText:context];
  168. if (_showScale)
  169. [self drawScale:context];
  170. if (_showRangeLabels)
  171. [self drawRangeLabels:context];
  172. }
  173. /**
  174. * Gauge external rim drawing
  175. */
  176. - (void)drawRim:(CGContextRef)context
  177. {
  178. // TODO
  179. // // 绘制内圈渐变色圆弧
  180. // CGContextSaveGState(context);
  181. //// CGContextRef ctx = UIGraphicsGetCurrentContext();
  182. // CGContextSetLineWidth(context, 18.0f);
  183. // //设置圆环线条的两个端点做圆滑处理
  184. // CGContextSetLineCap(context, kCGLineCapRound);
  185. // //设置画笔颜色
  186. // CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
  187. // //设置圆心
  188. // CGFloat originX = self.bounds.size.width / 2;
  189. // CGFloat originY = self.bounds.size.height / 2;
  190. // //计算半径
  191. // CGFloat radius = MIN(originX, originY) - 18/2.0 - 10;
  192. //
  193. // CGContextAddArc(context, originX, originY, radius, -M_PI_2 - 5, M_PI_2 + 5, NO);
  194. //
  195. // //2. 创建一个渐变色
  196. // CGFloat locations[2];
  197. // NSArray *locationArray = @[@0,@0.5];
  198. // NSArray *colorArray = @[(id)HexRGB(0xFF41D3).CGColor,(id)HexRGB(0x4EFFC2).CGColor];
  199. // for (NSInteger index = 0; index < 2; index++) {
  200. // locations[index] = [locationArray[index] floatValue];
  201. // }
  202. //
  203. // //创建RGB色彩空间,创建这个以后,context里面用的颜色都是用RGB表示
  204. // CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  205. // CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,(__bridge CFArrayRef _Nonnull)colorArray, locations);
  206. //
  207. // //释放色彩空间
  208. // CGColorSpaceRelease(colorSpace);
  209. // colorSpace = NULL;
  210. //
  211. // //3.画出圆环路径
  212. // CGContextReplacePathWithStrokedPath(context);
  213. // //剪裁路径
  214. // CGContextClip(context);
  215. //
  216. // //4.用渐变色填充,修改填充色的方向,_startPoint和_endPoint两个点的连线,就是颜色的分布方向
  217. // CGContextDrawLinearGradient(context, gradient, CGPointMake(10, self.bounds.size.height / 2), CGPointMake(self.bounds.size.width - 28, self.bounds.size.height / 2), 1);
  218. //
  219. // //释放渐变色
  220. // CGGradientRelease(gradient);
  221. }
  222. /**
  223. * Gauge inner background drawing
  224. */
  225. - (void)drawFace:(CGContextRef)context
  226. {
  227. if ([_style conformsToProtocol:@protocol(WMGaugeViewStyle)]) {
  228. [_style drawFaceWithContext:context inRect:faceRect];
  229. }
  230. }
  231. /**
  232. * Unit of measurement drawing
  233. */
  234. - (void)drawText:(CGContextRef)context
  235. {
  236. CGContextSetShadow(context, CGSizeMake(0.05, 0.05), 2.0);
  237. UIFont* font = _unitOfMeasurementFont ? _unitOfMeasurementFont : [UIFont fontWithName:@"Helvetica" size:0.04];
  238. UIColor* color = _unitOfMeasurementColor ? _unitOfMeasurementColor : [UIColor whiteColor];
  239. NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : color };
  240. NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:_unitOfMeasurement attributes:stringAttrs];
  241. CGSize fontWidth;
  242. fontWidth = [_unitOfMeasurement sizeWithAttributes:stringAttrs];
  243. [attrStr drawAtPoint:CGPointMake(0.5 - fontWidth.width / 2.0, _unitOfMeasurementVerticalOffset)];
  244. }
  245. /**
  246. * Scale drawing
  247. */
  248. - (void)drawScale:(CGContextRef)context
  249. {
  250. CGContextSaveGState(context);
  251. [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(180 + _scaleStartAngle)];
  252. int totalTicks = _scaleDivisions * _scaleSubdivisions + 1;
  253. for (int i = 0; i < totalTicks; i++)
  254. {
  255. CGFloat offset = 0.0;
  256. if (_scalesubdivisionsAligment == WMGaugeViewSubdivisionsAlignmentCenter) offset = (_scaleDivisionsLength - _scaleSubdivisionsLength) / 2.0;
  257. if (_scalesubdivisionsAligment == WMGaugeViewSubdivisionsAlignmentBottom) offset = _scaleDivisionsLength - _scaleSubdivisionsLength;
  258. CGFloat y1 = scaleRect.origin.y + 0.1;
  259. CGFloat y2 = y1 + _scaleSubdivisionsLength;
  260. CGFloat y3 = y1 - _scaleDivisionsLength;
  261. float value = [self valueForTick:i];
  262. float div = (_maxValue - _minValue) / _scaleDivisions;
  263. float mod = (int)value % (int)div;
  264. // Division
  265. if ((fabsf(mod - 0) < 0.000001) || (fabsf(mod - div) < 0.000001))
  266. {
  267. // Initialize Core Graphics settings
  268. UIColor *color = (_rangeValues && _rangeColors) ? [self rangeColorForValue:value] : _scaleDivisionColor;
  269. CGContextSetStrokeColorWithColor(context, color.CGColor);
  270. CGContextSetLineWidth(context, _scaleDivisionsWidth);
  271. CGContextSetShadow(context, CGSizeMake(0.05, 0.05), _showScaleShadow ? 2.0 : 0.0);
  272. // Draw tick
  273. CGContextMoveToPoint(context, 0.5, y1);
  274. CGContextAddLineToPoint(context, 0.5, y3);
  275. CGContextStrokePath(context);
  276. // Draw label
  277. if(_showScaleValues) {
  278. NSString *valueString = [NSString stringWithFormat:@"%0.0f",value];
  279. UIFont* font = _scaleFont ? _scaleFont : [UIFont fontWithName:@"Helvetica-Bold" size:0.05];
  280. NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : HexRGB(0x999999) };
  281. NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:valueString attributes:stringAttrs];
  282. CGSize fontWidth;
  283. fontWidth = [valueString sizeWithAttributes:stringAttrs];
  284. [attrStr drawAtPoint:CGPointMake(0.5 - fontWidth.width / 2.0, y3 - 0.065)];
  285. }
  286. }
  287. // Subdivision
  288. else
  289. {
  290. // Initialize Core Graphics settings
  291. UIColor *color = (_rangeValues && _rangeColors) ? [self rangeColorForValue:value] : _scaleSubDivisionColor;
  292. CGContextSetStrokeColorWithColor(context, color.CGColor);
  293. CGContextSetLineWidth(context, _scaleSubdivisionsWidth);
  294. CGContextMoveToPoint(context, 0.5, y1);
  295. if (_showScaleShadow) CGContextSetShadow(context, CGSizeMake(0.05, 0.05), 2.0);
  296. // Draw tick
  297. CGContextMoveToPoint(context, 0.5, y1 + offset);
  298. CGContextAddLineToPoint(context, 0.5, y2 + offset);
  299. CGContextStrokePath(context);
  300. }
  301. // Rotate to next tick
  302. [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(subdivisionAngle)];
  303. }
  304. CGContextRestoreGState(context);
  305. }
  306. /**
  307. * scale range labels drawing
  308. */
  309. - (void)drawRangeLabels:(CGContextRef)context
  310. {
  311. CGContextSaveGState(context);
  312. [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(90 + _scaleStartAngle)];
  313. CGContextSetShadow(context, CGSizeMake(0.0, 0.0), 0.0);
  314. CGFloat maxAngle = _scaleEndAngle - _scaleStartAngle;
  315. CGFloat lastStartAngle = 0.0f;
  316. for (int i = 0; i < _rangeValues.count; i ++)
  317. {
  318. // Range value
  319. float value = ((NSNumber*)[_rangeValues objectAtIndex:i]).floatValue;
  320. float valueAngle = (value - _minValue) / (_maxValue - _minValue) * maxAngle;
  321. // Range curved shape
  322. UIBezierPath *path = [UIBezierPath bezierPath];
  323. [path addArcWithCenter:center radius:rangeLabelsRect.size.width / 2.0 startAngle:DEGREES_TO_RADIANS(lastStartAngle) endAngle:DEGREES_TO_RADIANS(valueAngle) - 0.01 clockwise:YES];
  324. UIColor *color = _rangeColors[i];
  325. [color setStroke];
  326. path.lineWidth = _rangeLabelsWidth;
  327. [path stroke];
  328. // Range curved label
  329. [self drawStringAtContext:context string:(NSString*)_rangeLabels[i] withCenter:center radius:rangeLabelsRect.size.width / 2.0 startAngle:DEGREES_TO_RADIANS(lastStartAngle) endAngle:DEGREES_TO_RADIANS(valueAngle)];
  330. lastStartAngle = valueAngle;
  331. }
  332. CGContextRestoreGState(context);
  333. }
  334. /**
  335. * Needle drawing
  336. */
  337. - (void)drawNeedle
  338. {
  339. if ([_style conformsToProtocol:@protocol(WMGaugeViewStyle)]) {
  340. [_style drawNeedleOnLayer:rootNeedleLayer inRect:self.bounds];
  341. }
  342. }
  343. #pragma mark - Tools
  344. /**
  345. * Core Graphics rotation in context
  346. */
  347. - (void)rotateContext:(CGContextRef)context fromCenter:(CGPoint)center_ withAngle:(CGFloat)angle
  348. {
  349. CGContextTranslateCTM(context, center_.x, center_.y);
  350. CGContextRotateCTM(context, angle);
  351. CGContextTranslateCTM(context, -center_.x, -center_.y);
  352. }
  353. /**
  354. * Needle angle computation
  355. */
  356. - (CGFloat)needleAngleForValue:(double)value
  357. {
  358. return DEGREES_TO_RADIANS(_scaleStartAngle + (value - _minValue) / (_maxValue - _minValue) * (_scaleEndAngle - _scaleStartAngle)) + M_PI;
  359. }
  360. /**
  361. * Initialize scale helper values
  362. */
  363. - (void)initScale
  364. {
  365. scaleRotation = (int)(_scaleStartAngle + 180) % 360;
  366. divisionValue = (_maxValue - _minValue) / _scaleDivisions;
  367. subdivisionValue = divisionValue / _scaleSubdivisions;
  368. subdivisionAngle = (_scaleEndAngle - _scaleStartAngle) / (_scaleDivisions * _scaleSubdivisions);
  369. }
  370. /**
  371. * Scale tick value computation
  372. */
  373. - (float)valueForTick:(int)tick
  374. {
  375. return tick * (divisionValue / _scaleSubdivisions) + _minValue;
  376. }
  377. /**
  378. * Scale range label color for value
  379. */
  380. - (UIColor*)rangeColorForValue:(float)value
  381. {
  382. NSInteger length = _rangeValues.count;
  383. for (int i = 0; i < length - 1; i++)
  384. {
  385. if (value < [_rangeValues[i] floatValue])
  386. return _rangeColors[i];
  387. }
  388. if (value <= [_rangeValues[length - 1] floatValue])
  389. return _rangeColors[length - 1];
  390. return nil;
  391. }
  392. /**
  393. * Draw curved NSSring in context
  394. */
  395. - (void)drawStringAtContext:(CGContextRef) context string:(NSString*)text withCenter:(CGPoint)center_ radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle
  396. {
  397. CGContextSaveGState(context);
  398. UIFont* font = _rangeLabelsFont ? _rangeLabelsFont : [UIFont fontWithName:@"Helvetica" size:0.05];
  399. UIColor* color = _rangeLabelsFontColor ? _rangeLabelsFontColor : [UIColor whiteColor];
  400. //add extra character to ensure there is some padding
  401. NSString* testText = [text stringByAppendingString:@"$"];
  402. while(_adjustRangeLabelSizeToFitWidth) {
  403. CGFloat maxWidth = radius * (endAngle - startAngle);
  404. CGSize textSize = [testText boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
  405. options:NSStringDrawingUsesLineFragmentOrigin
  406. attributes:@{NSFontAttributeName: font}
  407. context:nil].size;
  408. if(textSize.height < _rangeLabelsWidth) {
  409. break;
  410. }
  411. font = [font fontWithSize:font.pointSize * 0.9];
  412. }
  413. NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : color };
  414. CGSize textSize = [text sizeWithAttributes:stringAttrs];
  415. float perimeter = 2 * M_PI * radius;
  416. float textAngle = textSize.width / perimeter * 2 * M_PI * _rangeLabelsFontKerning;
  417. float offset = ((endAngle - startAngle) - textAngle) / 2.0;
  418. float letterPosition = 0;
  419. NSString *lastLetter = @"";
  420. [self rotateContext:context fromCenter:center withAngle:startAngle + offset];
  421. for (int index = 0; index < [text length]; index++)
  422. {
  423. NSRange range = {index, 1};
  424. NSString* letter = [text substringWithRange:range];
  425. NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:letter attributes:stringAttrs];
  426. CGSize charSize = [letter sizeWithAttributes:stringAttrs];
  427. float totalWidth = [[NSString stringWithFormat:@"%@%@",lastLetter, letter] sizeWithAttributes:stringAttrs].width;
  428. float currentLetterWidth = [letter sizeWithAttributes:stringAttrs].width;
  429. float lastLetterWidth = [lastLetter sizeWithAttributes:stringAttrs].width;
  430. float kerning = (lastLetterWidth) ? 0.0 : ((currentLetterWidth + lastLetterWidth) - totalWidth);
  431. letterPosition += (charSize.width / 2) - kerning;
  432. float angle = (letterPosition / perimeter * 2 * M_PI) * _rangeLabelsFontKerning;
  433. CGPoint letterPoint = CGPointMake((radius - charSize.height / 2.0) * cos(angle) + center_.x, (radius - charSize.height / 2.0) * sin(angle) + center_.y);
  434. CGContextSaveGState(context);
  435. CGContextTranslateCTM(context, letterPoint.x, letterPoint.y);
  436. CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(angle + M_PI_2);
  437. CGContextConcatCTM(context, rotationTransform);
  438. CGContextTranslateCTM(context, -letterPoint.x, -letterPoint.y);
  439. [attrStr drawAtPoint:CGPointMake(letterPoint.x - charSize.width/2 , letterPoint.y - charSize.height)];
  440. CGContextRestoreGState(context);
  441. letterPosition += charSize.width / 2;
  442. lastLetter = letter;
  443. }
  444. CGContextRestoreGState(context);
  445. }
  446. /**
  447. * Invalidate background
  448. * Background will be regenerated during next draw rect
  449. */
  450. - (void)invalidateBackground
  451. {
  452. background = nil;
  453. [self initDrawingRects];
  454. [self initScale];
  455. [self setNeedsDisplay];
  456. }
  457. /**
  458. * Invalidate Needle
  459. * Needle will be regenerated during next draw rect
  460. */
  461. - (void)invalidateNeedle
  462. {
  463. [rootNeedleLayer removeAllAnimations];
  464. rootNeedleLayer.sublayers = nil;
  465. rootNeedleLayer = nil;
  466. [self setNeedsDisplay];
  467. }
  468. #pragma mark - Value update
  469. /**
  470. * Update gauge value
  471. */
  472. - (void)updateValue:(float)value
  473. {
  474. // Clamp value if out of range
  475. if (value > _maxValue)
  476. value = _maxValue;
  477. else if (value < _minValue)
  478. value = _minValue;
  479. else
  480. value = value;
  481. // Set value
  482. _value = value;
  483. }
  484. /**
  485. * Update gauge value with animation
  486. */
  487. - (void)setValue:(float)value animated:(BOOL)animated
  488. {
  489. [self setValue:value animated:animated duration:0.2];
  490. }
  491. /**
  492. * Update gauge value with animation and fire a completion block
  493. */
  494. - (void)setValue:(float)value animated:(BOOL)animated completion:(void (^)(BOOL finished))completion
  495. {
  496. [self setValue:value animated:animated duration:0.2 completion:completion];
  497. }
  498. /**
  499. * Update gauge value with animation and duration
  500. */
  501. - (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration
  502. {
  503. [self setValue:value animated:animated duration:duration completion:nil];
  504. }
  505. /**
  506. * Update gauge value with animation, duration and fire a completion block
  507. */
  508. - (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration completion:(void (^)(BOOL finished))completion
  509. {
  510. animationCompletion = completion;
  511. double lastValue = _value;
  512. [self updateValue:value];
  513. double middleValue = lastValue + (((lastValue + (_value - lastValue) / 2.0) >= 0) ? (_value - lastValue) / 2.0 : (lastValue - _value) / 2.0);
  514. // Needle animation to target value
  515. // An intermediate "middle" value is used to make sure the needle will follow the right rotation direction
  516. CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
  517. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  518. animation.removedOnCompletion = YES;
  519. animation.duration = animated ? duration : 0.0;
  520. animation.delegate = (id<CAAnimationDelegate>)self;
  521. animation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:lastValue] , 0, 0, 1.0)],
  522. [NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:middleValue], 0, 0, 1.0)],
  523. [NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:_value] , 0, 0, 1.0)]];
  524. if ([_style conformsToProtocol:@protocol(WMGaugeViewStyle)] == NO || [_style needleLayer:rootNeedleLayer willMoveAnimated:animated duration:duration animation:animation] == NO)
  525. {
  526. rootNeedleLayer.transform = [[animation.values lastObject] CATransform3DValue];
  527. [rootNeedleLayer addAnimation:animation forKey:kCATransition];
  528. }
  529. }
  530. #pragma mark - CAAnimation delegate
  531. - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
  532. {
  533. if (animationCompletion)
  534. animationCompletion(flag);
  535. animationCompletion = nil;
  536. }
  537. #pragma mark - Properties
  538. - (void)setValue:(float)value
  539. {
  540. [self setValue:value animated:YES];
  541. }
  542. - (void)setShowInnerBackground:(bool)showInnerBackground
  543. {
  544. _showInnerBackground = showInnerBackground;
  545. [self invalidateBackground];
  546. }
  547. - (void)setShowInnerRim:(bool)showInnerRim
  548. {
  549. _showInnerRim = showInnerRim;
  550. [self invalidateBackground];
  551. }
  552. - (void)setInnerRimWidth:(CGFloat)innerRimWidth
  553. {
  554. _innerRimWidth = innerRimWidth;
  555. [self invalidateBackground];
  556. }
  557. - (void)setInnerRimBordeWidth:(CGFloat)innerRimBorderWidth
  558. {
  559. _innerRimBorderWidth = innerRimBorderWidth;
  560. [self invalidateBackground];
  561. }
  562. - (void)setScalePosition:(CGFloat)scalePosition
  563. {
  564. _scalePosition = scalePosition;
  565. [self invalidateBackground];
  566. }
  567. - (void)setScaleStartAngle:(CGFloat)scaleStartAngle
  568. {
  569. _scaleStartAngle = scaleStartAngle;
  570. [self invalidateBackground];
  571. }
  572. - (void)setScaleEndAngle:(CGFloat)scaleEndAngle
  573. {
  574. _scaleEndAngle = scaleEndAngle;
  575. [self invalidateBackground];
  576. }
  577. - (void)setScaleDivisions:(CGFloat)scaleDivisions
  578. {
  579. _scaleDivisions = scaleDivisions;
  580. [self invalidateBackground];
  581. }
  582. - (void)setScaleSubdivisions:(CGFloat)scaleSubdivisions
  583. {
  584. _scaleSubdivisions = scaleSubdivisions;
  585. [self invalidateBackground];
  586. }
  587. - (void)setShowScaleShadow:(bool)showScaleShadow
  588. {
  589. _showScaleShadow = showScaleShadow;
  590. [self invalidateBackground];
  591. }
  592. - (void)setShowScale:(bool)showScale
  593. {
  594. _showScale = showScale;
  595. [self invalidateBackground];
  596. }
  597. -(void)setShowScaleValues:(bool)showScaleValues {
  598. _showScaleValues = showScaleValues;
  599. [self invalidateBackground];
  600. }
  601. - (void)setScalesubdivisionsAligment:(WMGaugeViewSubdivisionsAlignment)scalesubdivisionsAligment
  602. {
  603. _scalesubdivisionsAligment = scalesubdivisionsAligment;
  604. [self invalidateBackground];
  605. }
  606. - (void)setScaleDivisionsLength:(CGFloat)scaleDivisionsLength
  607. {
  608. _scaleDivisionsLength = scaleDivisionsLength;
  609. [self invalidateBackground];
  610. }
  611. - (void)setScaleDivisionsWidth:(CGFloat)scaleDivisionsWidth
  612. {
  613. _scaleDivisionsWidth = scaleDivisionsWidth;
  614. [self invalidateBackground];
  615. }
  616. - (void)setScaleSubdivisionsLength:(CGFloat)scaleSubdivisionsLength
  617. {
  618. _scaleSubdivisionsLength = scaleSubdivisionsLength;
  619. [self invalidateBackground];
  620. }
  621. - (void)setScaleSubdivisionsWidth:(CGFloat)scaleSubdivisionsWidth
  622. {
  623. _scaleSubdivisionsWidth = scaleSubdivisionsWidth;
  624. [self invalidateBackground];
  625. }
  626. - (void)setScaleDivisionColor:(UIColor *)scaleDivisionColor
  627. {
  628. _scaleDivisionColor = scaleDivisionColor;
  629. [self invalidateBackground];
  630. }
  631. - (void)setScaleSubDivisionColor:(UIColor *)scaleSubDivisionColor
  632. {
  633. _scaleSubDivisionColor = scaleSubDivisionColor;
  634. [self invalidateBackground];
  635. }
  636. - (void)setScaleFont:(UIFont *)scaleFont
  637. {
  638. _scaleFont = scaleFont;
  639. [self invalidateBackground];
  640. }
  641. - (void)setRangeLabelsWidth:(CGFloat)rangeLabelsWidth
  642. {
  643. _rangeLabelsWidth = rangeLabelsWidth;
  644. [self invalidateBackground];
  645. }
  646. - (void)setMinValue:(float)minValue
  647. {
  648. _minValue = minValue;
  649. [self invalidateBackground];
  650. }
  651. - (void)setMaxValue:(float)maxValue
  652. {
  653. _maxValue = maxValue;
  654. [self invalidateBackground];
  655. }
  656. - (void)setRangeValues:(NSArray *)rangeValues
  657. {
  658. _rangeValues = rangeValues;
  659. [self invalidateBackground];
  660. }
  661. - (void)setRangeColors:(NSArray *)rangeColors
  662. {
  663. _rangeColors = rangeColors;
  664. [self invalidateBackground];
  665. }
  666. - (void)setRangeLabels:(NSArray *)rangeLabels
  667. {
  668. _rangeLabels = rangeLabels;
  669. [self invalidateBackground];
  670. }
  671. - (void)setUnitOfMeasurement:(NSString *)unitOfMeasurement
  672. {
  673. _unitOfMeasurement = unitOfMeasurement;
  674. [self invalidateBackground];
  675. }
  676. - (void)setShowUnitOfMeasurement:(bool)showUnitOfMeasurement
  677. {
  678. _showUnitOfMeasurement = showUnitOfMeasurement;
  679. [self invalidateBackground];
  680. }
  681. - (void)setShowRangeLabels:(bool)showRangeLabels
  682. {
  683. _showRangeLabels = showRangeLabels;
  684. [self invalidateBackground];
  685. }
  686. - (void)setRangeLabelsFontKerning:(CGFloat)rangeLabelsFontKerning
  687. {
  688. _rangeLabelsFontKerning = rangeLabelsFontKerning;
  689. [self invalidateBackground];
  690. }
  691. - (void)setUnitOfMeasurementFont:(UIFont *)unitOfMeasurementFont
  692. {
  693. _unitOfMeasurementFont = unitOfMeasurementFont;
  694. [self invalidateBackground];
  695. }
  696. - (void)setRangeLabelsFont:(UIFont *)rangeLabelsFont
  697. {
  698. _rangeLabelsFont = rangeLabelsFont;
  699. [self invalidateBackground];
  700. }
  701. - (void)setUnitOfMeasurementVerticalOffset:(CGFloat)unitOfMeasurementVerticalOffset
  702. {
  703. _unitOfMeasurementVerticalOffset = unitOfMeasurementVerticalOffset;
  704. [self invalidateBackground];
  705. }
  706. - (void)setUnitOfMeasurementColor:(UIColor *)unitOfMeasurementColor
  707. {
  708. _unitOfMeasurementColor = unitOfMeasurementColor;
  709. [self invalidateBackground];
  710. }
  711. - (void)setRangeLabelsFontColor:(UIColor *)rangeLabelsFontColor
  712. {
  713. _rangeLabelsFontColor = rangeLabelsFontColor;
  714. [self invalidateBackground];
  715. }
  716. - (void)setStyle:(id<WMGaugeViewStyle>)style {
  717. _style = style;
  718. [self invalidateBackground];
  719. [self invalidateNeedle];
  720. }
  721. @end