123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- //
- // KSGaugeView.m
- // KulexiuForStudent
- //
- // Created by 王智 on 2022/10/25.
- //
- #import "KSGaugeView.h"
- #import "KSGaugeColorView.h"
- #import "KSGaugeSectorView.h"
- /* Degrees to radians conversion macro */
- #define DEGREES_TO_RADIANS(degrees) (degrees) / 180.0 * M_PI
- @interface KSGaugeView ()
- {
- /* View center */
- CGPoint center;
- /* Needle layer */
- CALayer *rootNeedleLayer;
- /* Annimation completion */
- void (^animationCompletion)(BOOL);
-
- /* Scale variables */
- CGFloat scaleRotation;
- CGFloat divisionValue;
- CGFloat subdivisionValue;
- CGFloat subdivisionAngle;
- }
- @end
- @implementation KSGaugeView
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self)
- {
- self.backgroundColor = [UIColor clearColor];
- [self initialize];
- }
- return self;
- }
- - (void)initialize {
- _scaleStartAngle = 90;
- _scaleEndAngle = 270;
- _maxValue = 50.0;
- _minValue = -50.0;
- _scaleDivisions = 10;
- _scaleSubdivisions = 10;
- _scaleDivisionsWidth = 2;
- _scaleDivisionsLength = 15;
- _scaleSubdivisionsWidth = 1;
- _scaleSubdivisionsLength = 8;
- [self initScale];
- [self addShaperView];
- [self addColorView];
- }
- // 添加扇形区域
- - (void)addShaperView {
- KSGaugeSectorView *sectorView = [[KSGaugeSectorView alloc] init];
- sectorView.backgroundColor = [UIColor clearColor];
- [self addSubview:sectorView];
- [sectorView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.top.bottom.left.right.mas_equalTo(self);
- }];
- }
- - (void)addColorView {
- KSGaugeColorView *colorView = [[KSGaugeColorView alloc] init];
- colorView.backgroundColor = [UIColor clearColor];
- [self addSubview:colorView];
- [colorView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.top.bottom.left.right.mas_equalTo(self);
- }];
- }
- - (void)initScale {
- scaleRotation = (int)(_scaleStartAngle + 180) % 360;
- divisionValue = (_maxValue - _minValue) / _scaleDivisions;
- subdivisionValue = divisionValue / _scaleSubdivisions;
- subdivisionAngle = (_scaleEndAngle - _scaleStartAngle) / (_scaleDivisions * _scaleSubdivisions);
- }
- - (void)drawRect:(CGRect)rect {
- CGFloat originX = rect.size.width / 2.0f;
- CGFloat originY = rect.size.height - 25;
- center = CGPointMake(originX, originY);
- CGContextRef context = UIGraphicsGetCurrentContext();
- // 绘制lable
- [self drawTextLabel:rect contenx:context];
- // 绘制表盘
- [self drawDevision:rect context:context];
- // 绘制指针
- if (![self.layer.sublayers containsObject:rootNeedleLayer]) {
- rootNeedleLayer = [CALayer new];
- // For performance puporse, the needle layer is not scaled to [0-1] range
- rootNeedleLayer.frame = CGRectMake(0, 40, rect.size.width, (originY - 40) * 2);
- [self.layer addSublayer:rootNeedleLayer];
- }
-
- [self drawNeedle:rect context:context];
- }
- - (void)drawNeedle:(CGRect)rect context:(CGContextRef)context {
-
- CGFloat originX = rect.size.width / 2.0f;
- CGFloat originY = rect.size.height - 25;
- center = CGPointMake(originX, originY);
-
- // Left Needle
- CAShapeLayer *needleLayer = [CAShapeLayer layer];
- UIBezierPath *needlePath = [UIBezierPath bezierPath];
- [needlePath moveToPoint:CGPointMake(originX, originY - 40)];
- [needlePath addLineToPoint:CGPointMake(originX-1, originY-40)];
- [needlePath addLineToPoint:CGPointMake(originX-1, 0)];
- [needlePath addLineToPoint:CGPointMake(originX+1, 0)];
- [needlePath addLineToPoint:CGPointMake(originX+1, originY-40)];
- [needlePath addLineToPoint:CGPointMake(originX, originY-40)];
- [needlePath closePath];
-
- needleLayer.path = needlePath.CGPath;
- needleLayer.backgroundColor = [[UIColor clearColor] CGColor];
- needleLayer.fillColor = [UIColor whiteColor].CGColor;
- [rootNeedleLayer addSublayer:needleLayer];
- }
- - (void)drawTextLabel:(CGRect)rect contenx:(CGContextRef)context {
- int totalTicks = _scaleDivisions * _scaleSubdivisions + 1;
- for (int i = 0; i < totalTicks; i++) {
- float value = [self valueForTick:i];
- float div = (_maxValue - _minValue) / _scaleDivisions;
- float mod = (int)value % (int)div;
- //
- if ((fabsf(mod - 0) < 0.000001) || (fabsf(mod - div) < 0.000001)) {
-
- NSString *valueString = [NSString stringWithFormat:@"%0.0f",value];
- UIFont* font = [UIFont fontWithName:@"DIN Alternate Bold" size:14.0f];
- NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : HexRGB(0x999999) };
- NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:valueString attributes:stringAttrs];
- CGSize fontSize;
- fontSize = [valueString sizeWithAttributes:stringAttrs];
- CGFloat radius = rect.size.height - 30;
- CGFloat angle = 180 - subdivisionAngle*i;
- CGPoint drawPoint = [self calcCicleCoordinateWithCenter:center angle:angle radius:radius];
- CGPoint lablePoint = CGPointZero;
- if (angle > -90) {
- lablePoint = CGPointMake(drawPoint.x - fontSize.width/2, drawPoint.y - fontSize.height/2);
- }
-
- else {
- lablePoint = CGPointMake(drawPoint.x + fontSize.width/2, drawPoint.y + fontSize.height/2);
- }
- [attrStr drawAtPoint:lablePoint];
- }
- }
- }
- - (CGPoint)calcCicleCoordinateWithCenter:(CGPoint)centerPoint angle:(CGFloat)angle radius:(CGFloat)radius {
- CGFloat positionX = radius * cosf(angle*M_PI/180);
- CGFloat positionY = radius * sinf(angle*M_PI/180);
- return CGPointMake(centerPoint.x + positionX, centerPoint.y-positionY);
- }
- - (void)drawDevision:(CGRect)rect context:(CGContextRef)context {
-
- [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(180 + _scaleStartAngle)];
- int totalTicks = _scaleDivisions * _scaleSubdivisions + 1;
- for (int i = 0; i < totalTicks; i++) {
- CGFloat y1 = 18;
- CGFloat y2 = y1 + _scaleSubdivisionsLength;
- CGFloat y3 = y1 + _scaleDivisionsLength;
-
- float value = [self valueForTick:i];
- float div = (_maxValue - _minValue) / _scaleDivisions;
- float mod = (int)value % (int)div;
-
- // Division
- if ((fabsf(mod - 0) < 0.000001) || (fabsf(mod - div) < 0.000001))
- {
- // Initialize Core Graphics settings
- UIColor *color = HexRGB(0x2dc7aa);
- CGContextSetStrokeColorWithColor(context, color.CGColor);
- CGContextSetLineWidth(context, _scaleDivisionsWidth);
-
- // Draw tick
- CGContextMoveToPoint(context, center.x, y1);
- CGContextAddLineToPoint(context, center.x, y3);
- CGContextStrokePath(context);
-
- // 绘制文字
- // NSString *valueString = [NSString stringWithFormat:@"%0.0f",value];
- // UIFont* font = [UIFont fontWithName:@"DIN Alternate Bold" size:14.0f];
- // NSDictionary* stringAttrs = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : HexRGB(0x999999) };
- // NSAttributedString* attrStr = [[NSAttributedString alloc] initWithString:valueString attributes:stringAttrs];
- // CGSize fontWidth;
- // fontWidth = [valueString sizeWithAttributes:stringAttrs];
- // [attrStr drawAtPoint:CGPointMake(center.x - fontWidth.width / 2.0, y1 - _scaleDivisionsLength - 5)];
-
- }
- else {
- // Initialize Core Graphics settings
- UIColor *color = HexRGBAlpha(0x2dc7aa, 0.5);
- CGContextSetStrokeColorWithColor(context, color.CGColor);
- CGContextSetLineWidth(context, _scaleSubdivisionsWidth);
- CGContextMoveToPoint(context, center.x, y1);
-
- // Draw tick
- CGContextMoveToPoint(context, center.x, y1);
- CGContextAddLineToPoint(context, center.x, y2);
- CGContextStrokePath(context);
- }
- // Rotate to next tick
- [self rotateContext:context fromCenter:center withAngle:DEGREES_TO_RADIANS(subdivisionAngle)];
- }
-
- }
- /**
- * Scale tick value computation
- */
- - (float)valueForTick:(int)tick
- {
- return tick * (divisionValue / _scaleSubdivisions) + _minValue;
- }
- #pragma mark - Tools
- /**
- * Core Graphics rotation in context
- */
- - (void)rotateContext:(CGContextRef)context fromCenter:(CGPoint)center_ withAngle:(CGFloat)angle
- {
- CGContextTranslateCTM(context, center_.x, center_.y);
- CGContextRotateCTM(context, angle);
- CGContextTranslateCTM(context, -center_.x, -center_.y);
- }
- /**
- * Needle angle computation
- */
- - (CGFloat)needleAngleForValue:(double)value
- {
- return DEGREES_TO_RADIANS(_scaleStartAngle + (value - _minValue) / (_maxValue - _minValue) * (_scaleEndAngle - _scaleStartAngle)) + M_PI;
- }
- #pragma mark - Value update
- /**
- * Update gauge value
- */
- - (void)updateValue:(float)value
- {
- // Clamp value if out of range
- if (value > _maxValue)
- value = _maxValue;
- else if (value < _minValue)
- value = _minValue;
- else
- value = value;
-
- // Set value
- _value = value;
- }
- /**
- * Update gauge value with animation
- */
- - (void)setValue:(float)value animated:(BOOL)animated
- {
- [self setValue:value animated:animated duration:0.2];
- }
- /**
- * Update gauge value with animation and fire a completion block
- */
- - (void)setValue:(float)value animated:(BOOL)animated completion:(void (^)(BOOL finished))completion
- {
- [self setValue:value animated:animated duration:0.2 completion:completion];
- }
- /**
- * Update gauge value with animation and duration
- */
- - (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration
- {
- [self setValue:value animated:animated duration:duration completion:nil];
- }
- /**
- * Update gauge value with animation, duration and fire a completion block
- */
- - (void)setValue:(float)value animated:(BOOL)animated duration:(NSTimeInterval)duration completion:(void (^)(BOOL finished))completion
- {
- animationCompletion = completion;
-
- double lastValue = _value;
-
- [self updateValue:value];
- double middleValue = lastValue + (((lastValue + (_value - lastValue) / 2.0) >= 0) ? (_value - lastValue) / 2.0 : (lastValue - _value) / 2.0);
- if (middleValue > 50) {
- middleValue = 50;
- }
- else if (middleValue < -50) {
- middleValue = -50;
- }
- // Needle animation to target value
- // An intermediate "middle" value is used to make sure the needle will follow the right rotation direction
-
- CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
- animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
- animation.removedOnCompletion = YES;
- animation.duration = animated ? duration : 0.0;
- animation.delegate = (id<CAAnimationDelegate>)self;
- animation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:lastValue] , 0, 0, 1.0)],
- [NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:middleValue], 0, 0, 1.0)],
- [NSValue valueWithCATransform3D:CATransform3DMakeRotation([self needleAngleForValue:_value] , 0, 0, 1.0)]];
- rootNeedleLayer.transform = [[animation.values lastObject] CATransform3DValue];
- [rootNeedleLayer addAnimation:animation forKey:kCATransition];
-
- }
- #pragma mark - CAAnimation delegate
- - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
- {
- if (animationCompletion)
- animationCompletion(flag);
-
- animationCompletion = nil;
- }
- #pragma mark - Properties
- - (void)setValue:(float)value
- {
- [self setValue:value animated:YES];
- }
- /*
- // Only override drawRect: if you perform custom drawing.
- // An empty implementation adversely affects performance during animation.
- - (void)drawRect:(CGRect)rect {
- // Drawing code
- }
- */
- @end
|