IOS custom watch face, scale value and pointer support color gradient

Due to business needs, a customized dial control
1: Scale values and pointers support color gradients
2: Because I wrote it all by myself and use it myself, there are not many ways to modify it. If you want to use it, you can take a good look at the source code. The implementation principle is actually not difficult, just learn it.

android version: https://blog.csdn.net/qq_15327175/article/details/134155127

Look at the renderings, animations and numerical changes. I won’t show them here. Friends with similar needs can do their own research.

WBBDashBoard.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface WBBDashBoard : UIView

@property (nonatomic, assign) float value;

@end

NS_ASSUME_NONNULL_END

WBBDashBoard.m

#import "WBBDashBoard.h"
#define toRad(angle) ((angle) * M_PI / 180)
@interface WBBDashBoard ()

/**
 * pointer center
 */
@property (nonatomic, assign) CGPoint dotCenter;
/**
 *Instrument radius
 */
@property (nonatomic, assign) CGFloat radius;
/**
 * Disk starting angle
 */
@property(nonatomic,assign)CGFloat startAngle;
/**
 * Disc end angle
 */
@property(nonatomic,assign)CGFloat endAngle;
/**
 * The total number of radians of the disk
 */
@property(nonatomic,assign)CGFloat arcAngle;

/**
 * pointer
 */
@property (nonatomic, strong) CAShapeLayer *pointLayer;
/**
 * Pointer fixed point
 */
@property (nonatomic, strong) CAShapeLayer *bPointLayer;

@property (nonatomic, strong) UILabel *valueLable;

/**
 * Dial value width
 */
@property(nonatomic,assign) CGFloat valueW;
/**
 * Dial value is high
 */
@property(nonatomic,assign)CGFloat valueH;

/**
 *maximum
 */
@property(nonatomic,assign)CGFloat maxValue;
/**
 * minimum value
 */
@property(nonatomic,assign)CGFloat minValue;

/**
 * Gradient circle
 */
@property (nonatomic, strong)CAShapeLayer* arcLayer;

@end

@implementation WBBDashBoard

/*
 // Only override drawRect: if you perform custom drawing.
 // An empty implementation adversely affects performance during animation.
 - (void)drawRect:(CGRect)rect {
 // Drawing code
 }
 */

- (instancetype)initWithFrame:(CGRect)frame {<!-- -->
    if (self = [super initWithFrame:frame]) {<!-- -->
        _value=1.0f;
        self.dotCenter = CGPointMake(frame.size.width/2.0, frame.size.width/2.0);
        self.radius = frame.size.width/2.0;
        self.startAngle=toRad(-225);
        self.endAngle=toRad(45);
        self.valueW=24.f;
        self.valueH=16.f;
        self.maxValue=3.0f;
        self.minValue=1.0f;
        self.arcAngle= fabsf(self.startAngle - self.endAngle);
        
        [self loadSubViews];
    }
    
    return self;
}

- (void)loadSubViews {<!-- -->
    
    [self drawArcWithStartAngle];
    [self DrawScaleValue];
    [self drawPoint];
    [self setTitle:@"PUE"];
}

/**
 * Draw arc
 *
 * @param startAngle starting angle
 * @param endAngle end angle
 * @param lineWitdth line width
 * @param filleColor sector fill color
 * @param strokeColor arc color
 */
-(void)drawArcWithStartAngle{<!-- -->
    //Inner loop Mask
    UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius startAngle:_startAngle endAngle:_endAngle clockwise:YES];
    CAShapeLayer* shapeLayer1=[CAShapeLayer layer];
    shapeLayer1.lineWidth=3;
    shapeLayer1.fillColor=[UIColor clearColor].CGColor;
    shapeLayer1.path=outArc.CGPath;
    shapeLayer1.strokeColor=[UIColor whiteColor].CGColor;
    [self.layer addSublayer:shapeLayer1];
    double ArcLenght=0;
    // //Mask
    outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-4 startAngle:_startAngle endAngle:_endAngle clockwise:YES];
    CAShapeLayer* shapeLayer2=[CAShapeLayer layer];
    shapeLayer2.lineWidth=4;
    shapeLayer2.fillColor=[UIColor clearColor].CGColor;
    shapeLayer2.path=outArc.CGPath;
    ArcLenght=((2*M_PI*(_radius-4)*3/4)-21*1)/20;
    [shapeLayer2 setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithDouble:1], [NSNumber numberWithDouble:ArcLenght], nil]];//0: length 1: gap
    shapeLayer2.strokeColor=[UIColor whiteColor].CGColor;
    [self.layer addSublayer:shapeLayer2];
    
    
    outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-4 startAngle:_startAngle endAngle:_endAngle clockwise:YES];
    CAShapeLayer* shapeLayer3=[CAShapeLayer layer];
    shapeLayer3.lineWidth=6;
    shapeLayer3.fillColor=[UIColor clearColor].CGColor;
    shapeLayer3.path=outArc.CGPath;
    ArcLenght=((2*M_PI*(_radius-4)*3/4)-5*2)/4;
    [shapeLayer3 setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithDouble:2], [NSNumber numberWithDouble:ArcLenght], nil]];//0: length 1: gap
    shapeLayer3.strokeColor=[UIColor whiteColor].CGColor;
    [self.layer addSublayer:shapeLayer3];
    
}

/**
 * Draw the scale value, set the label value counterclockwise, divide the entire instrument into N parts, and increment by 1/N of the instrument panel radian each time
 *
 * @param divide how many equal parts the scale value is
 */
-(void)DrawScaleValue{<!-- -->
    CGFloat textAngel =self.arcAngle/4;
    
    for (NSUInteger i = 0; i <= 4; i + + ) {<!-- -->
        CGPoint point = [self calculateTextPositonWithArcCenter:_dotCenter Angle:(self.startAngle + textAngel*i)];
        NSString *tickText;
        if (i == 0) {<!-- -->
            tickText =@"1.0";
        } else if (i == 4) {<!-- -->
            tickText =@"3.0";
        } else {<!-- -->
            tickText = [NSString stringWithFormat:@"%.1f",(3.0f-1.0f)/4.0f*i + 1.0f];
        }
        //The default label size is 23 * 14
        UILabel *text = [[UILabel alloc] initWithFrame:CGRectMake(point.x, point.y, _valueW, _valueH)];
        text.text = tickText;
        text.font = [UIFont systemFontOfSize:12.f];
        text.textColor = [UIColor whiteColor];
        // text.backgroundColor=[UIColor greenColor];
        text.textAlignment = NSTextAlignmentCenter;
        [self addSubview:text];
    }
}
//The default calculation radius is -10, and the coordinates of the label are calculated
- (CGPoint)calculateTextPositonWithArcCenter:(CGPoint)center
                                       Angle:(CGFloat)angel
{<!-- -->
    
    CGFloat x = center.x + (_radius)* cosf(angel);
    CGFloat y = center.y + (_radius)* sinf(angel);
    
    // y=y-_valueH/2;
    if(angel>=toRad(5) & amp; & amp;angel<=toRad(85)||angel>=toRad(-275) & amp; & amp;angel<=toRad(-355)){<! -- -->
        y=y-_valueH/2;
        x=x + _valueW/3;
    }else if(angel>toRad(85) & amp; & amp;angel<toRad(95)||angel>toRad(-265) & amp; & amp;angel<toRad(-275)){<!-- -->
        y=y-_valueH/2 + _valueW/3;
        x=x-x + _valueW/2;
    }
    else if(angel>=toRad(95) & amp; & amp;angel<=toRad(175)||angel>=toRad(-265) & amp; & amp;angel<=toRad(-185)){< !-- -->
        y=y-_valueH/2;
        x=x-_valueW-_valueW/3;
    }else if(angel>toRad(175) & amp; & amp;angel<toRad(185)||angel>toRad(-185) & amp; & amp;angel<toRad(-175)){<!-- -->
        y=y-_valueH/2;
        x=x-_valueW-_valueW/3;
    }
    else if(angel>=toRad(185) & amp; & amp;angel<=toRad(265)||angel>=toRad(-175) & amp; & amp;angel<=toRad(-95)){< !-- -->
        y=y-_valueH/2;
        x=x-_valueW-_valueW/3;
    }else if(angel>toRad(265) & amp; & amp;angel<toRad(275)||angel>toRad(-95) & amp; & amp;angel<toRad(-85)){<!-- -->
        y=y-_valueH;
        x=x-_valueW/2;
    }
    else if(angel>=toRad(275) & amp; & amp;angel<=toRad(355)||angel>=toRad(-85) & amp; & amp;angel<=toRad(-5)){< !-- -->
        y=y-_valueH/2;
        x=x + _valueW/3;
    }else if(angel>toRad(355) & amp; & amp;angel<toRad(5)||angel>toRad(-5) & amp; & amp;angel<toRad(5)){<!-- - ->
        y=y-_valueH/2;
        x=x + _valueW/3;
    }
    // NSLog([NSString stringWithFormat:@"0:%f",toRad(0)]);
    // NSLog(@"-------------------------------------------------- ----------------------------------");
    // NSLog([NSString stringWithFormat:@"90:%f",toRad(90)]);
    // NSLog([NSString stringWithFormat:@"180:%f",toRad(180)]);
    // NSLog([NSString stringWithFormat:@"270:%f",toRad(270)]);
    // NSLog(@"-------------------------------------------------- ----------------------------------");
    // NSLog([NSString stringWithFormat:@"-90:%f",toRad(-90)]);
    // NSLog([NSString stringWithFormat:@"-180:%f",toRad(-180)]);
    // NSLog([NSString stringWithFormat:@"-270:%f",toRad(-270)]);
    
    return CGPointMake(x, y);

}
-(void)setTitle:(NSString *)title {<!-- -->
    
    UILabel *text = [[UILabel alloc] initWithFrame:CGRectMake(self.dotCenter.x-25, self.dotCenter.y + 20, 50, 20)];
    text.text = title;
    text.font = [UIFont systemFontOfSize:12.f];
    text.textColor = [UIColor whiteColor];
    // text.backgroundColor=[UIColor greenColor];
    text.textAlignment = NSTextAlignmentCenter;
    [self addSubview:text];
    
    _valueLable=[[UILabel alloc] initWithFrame:CGRectMake(self.dotCenter.x-25, self.dotCenter.y + 40, 50, 20)];
    _valueLable.text=@"--";
    _valueLable.textAlignment=NSTextAlignmentCenter;
    _valueLable.font=[UIFont systemFontOfSize:14];
    _valueLable.textColor=[UIColor whiteColor];
    [self addSubview:_valueLable];
    
}

- (void)drawPoint {<!-- -->
    
    UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-3 startAngle:_startAngle endAngle:_startAngle clockwise:YES];
    _arcLayer=[CAShapeLayer layer];
    _arcLayer.lineWidth=6;
    _arcLayer.fillColor=[UIColor clearColor].CGColor;
    _arcLayer.path=outArc.CGPath;
    _arcLayer.strokeColor=[UIColor whiteColor].CGColor;
  
    NSArray *colors1= [NSArray arrayWithObjects:
                       (id)[[UIColor kh_colorWithHexString:@"36e6fd" andAlpha:0.2] CGColor],
                       (id)[[UIColor kh_colorWithHexString:@"60df5a" andAlpha:0.6] CGColor],
                       (id)[[UIColor kh_colorWithHexString:@"efdf1b" andAlpha:0.7] CGColor],nil];
    NSArray *colors2= [NSArray arrayWithObjects:
                       (id)[[UIColor kh_colorWithHexString:@"efdf1b" andAlpha:0.7] CGColor],
                       (id)[[UIColor kh_colorWithHexString:@"efdf1b" andAlpha:0.8] CGColor],
                       (id)[[UIColor kh_colorWithHexString:@"f54f0A" andAlpha:0.8] CGColor],nil];
    //gradient layer
    CALayer *gradientLayer = [CALayer layer];
    CAGradientLayer *gradientLayer1 = [CAGradientLayer layer];
    gradientLayer1.frame = CGRectMake(-3, -3, (self.frame.size.width + 6)/2, self.frame.size.width + 6);
    gradientLayer1.shadowPath=outArc.CGPath;
    [gradientLayer1 setColors:colors1];
    [gradientLayer1 setStartPoint:CGPointMake(0.3, 0.8)];
    [gradientLayer1 setEndPoint:CGPointMake(1.0, 0.0)];

    [gradientLayer addSublayer:gradientLayer1];
    
    CAGradientLayer *gradientLayer2 = [CAGradientLayer layer];
    gradientLayer2.frame = CGRectMake(-3 + (self.frame.size.width + 6)/2, -3, (self.frame.size.width + 6)/2, self.frame.size.width + 6) ;
    gradientLayer2.shadowPath=outArc.CGPath;
    [gradientLayer2 setColors:colors2];
    [gradientLayer2 setStartPoint:CGPointMake(0.0, 0.0)];
    [gradientLayer2 setEndPoint:CGPointMake(0.8, 0.7)];
    
    [gradientLayer addSublayer:gradientLayer2];
    
    [gradientLayer setMask:_arcLayer]; //Use shapeLayer to intercept the gradient layer
    
    [self.layer addSublayer:gradientLayer];
    
    UIBezierPath* aPath = [UIBezierPath bezierPath];
    aPath.lineWidth = 15.0;
    aPath.lineCapStyle = kCGLineCapRound; //End point (start point) style
    aPath.lineJoinStyle = kCGLineCapRound; //Inflection point style
    // [aPath moveToPoint:CGPointMake(0, 0)]; //Set the starting point
    // [aPath addLineToPoint:CGPointMake(8, _radius-20)];//Passpoint
    // [aPath addLineToPoint:CGPointMake(0, _radius-20 + 15)];//Passing point
    // [aPath addLineToPoint:CGPointMake(-8, _radius-20)];//Passpoint
    
    [aPath moveToPoint:CGPointMake(0, 0)];//Set the starting point>>>>>>>>The point that needs to be rotated as the starting point
    [aPath addLineToPoint:CGPointMake(0, 5)];//Passpoint
    [aPath addLineToPoint:CGPointMake(-5, 0)];//Passpoint
    [aPath addLineToPoint:CGPointMake(0, -(_radius-15))];//Passpoint
    [aPath addLineToPoint:CGPointMake(5, 0)];//Passpoint
    [aPath addLineToPoint:CGPointMake(0, 5)];//Passpoint
    [aPath moveToPoint:CGPointMake(0, -(_radius-2))];
    [aPath addArcWithCenter:CGPointMake(0, -(_radius-2)) radius:4 startAngle:0 endAngle:toRad(360) clockwise:YES];
    [aPath closePath];//Get the last line by calling the closePath method
    
    self.pointLayer=[CAShapeLayer layer];
    self.pointLayer.lineWidth=3;
    self.pointLayer.fillColor=[UIColor kh_colorWithHexString:@"37E5FE"].CGColor;
    self.pointLayer.path=aPath.CGPath;
    self.pointLayer.strokeColor=[UIColor clearColor].CGColor;
    self.pointLayer.position = CGPointMake(self.dotCenter.x, self.dotCenter.y);//Move the aPath starting point to the center point so that it can rotate normally ()
    // shapeLayer1.anchorPoint = CGPointMake(1.0, 1.0); //Invalid
    
    UIBezierPath *dPath = [UIBezierPath bezierPath];
    dPath.lineWidth = 5.0;
    dPath.lineCapStyle = kCGLineCapRound; //Line corner
    dPath.lineJoinStyle = kCGLineCapRound; //End point processing
    [dPath moveToPoint:CGPointMake(0, -(_radius-2))];
    [dPath addArcWithCenter:CGPointMake(0, -(_radius-2)) radius:2 startAngle:0 endAngle:toRad(360) clockwise:YES];
    CAShapeLayer *dPointLayer=[CAShapeLayer layer];
    dPointLayer.lineWidth=3;
    dPointLayer.fillColor=[UIColor whiteColor].CGColor;
    dPointLayer.path=dPath.CGPath;
    dPointLayer.strokeColor=[UIColor clearColor].CGColor;
    [self.pointLayer addSublayer: dPointLayer];
    
    
    [self.layer addSublayer: self.pointLayer];
    
    
    CGFloat angle1 = toRad(-135);
    self.pointLayer.transform = CATransform3DMakeRotation(angle1, 0, 0, 1);
    
    UIBezierPath *bPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_dotCenter.x-5, _dotCenter.y-5, 10, 10)];
    bPath.lineWidth = 5.0;
    bPath.lineCapStyle = kCGLineCapRound; //Line corner
    bPath.lineJoinStyle = kCGLineCapRound; //End point processing
    self.bPointLayer=[CAShapeLayer layer];
    self.bPointLayer.lineWidth=3;
    self.bPointLayer.fillColor=[UIColor kh_colorWithHexString:@"37E5FE"].CGColor;
    self.bPointLayer.path=bPath.CGPath;
    self.bPointLayer.strokeColor=[UIColor clearColor].CGColor;
    [self.layer addSublayer: self.bPointLayer];
    
    UIBezierPath *cPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_dotCenter.x-2.5, _dotCenter.y-2.5, 5, 5)];
    cPath.lineWidth = 5.0;
    cPath.lineCapStyle = kCGLineCapRound; //Line corner
    cPath.lineJoinStyle = kCGLineCapRound; //End point processing
    CAShapeLayer *cPointLayer=[CAShapeLayer layer];
    cPointLayer.lineWidth=3;
    cPointLayer.fillColor=[UIColor whiteColor].CGColor;
    cPointLayer.path=cPath.CGPath;
    cPointLayer.strokeColor=[UIColor clearColor].CGColor;
    [self.layer addSublayer: cPointLayer];
    
    
    
    
    //
    // CGFloat angle2 = (1.8 - 1.6)/1.2*M_PI;
    // self.pointLayer.transform = CATransform3DMakeRotation(angle2, 0, 0, 1);
    //
    // CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    // animation.duration = 1.0f;
    // animation.fromValue = @(angle1);
    // animation.toValue = @(angle2);
    // animation.fillMode = kCAFillModeForwards;
    // animation.removedOnCompletion = NO;
    // animation.autoreverses = NO;
    // animation.timingFunction=[CAMediaTimingFunction functionWithControlPoints:0.20 :1.50 :0.20 :1.00];
    // animation.repeatCount = 1;
    //
    // // CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    // // animation2.duration = 2.f;
    // animation2.fromValue = @(angle2 + 0.1*M_PI);
    // // animation2.toValue = @(angle2-0.1*M_PI);
    // // animation2.fillMode = kCAFillModeForwards;
    // // animation2.removedOnCompletion = NO;
    // // animation2.repeatCount = 1;
    //
    // // CABasicAnimation *animation3 = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    // // animation3.duration = 2.f;
    // // animation3.fromValue = @(angle2-0.2*M_PI);
    // // animation3.toValue = @(angle2);
    // // animation3.fillMode = kCAFillModeForwards;
    // // animation3.removedOnCompletion = NO;
    // // animation3.repeatCount = 1;
    // //
    // // CAAnimationGroup *groupAnnimation = [CAAnimationGroup animation];
    // // groupAnnimation.duration = 2.0f;
    // // groupAnnimation.autoreverses = NO;// Whether the animation will be executed in reverse after completion
    // // groupAnnimation.animations = @[animation];
    // // groupAnnimation.repeatCount = 1;
    // [self.pointLayer addAnimation:animation forKey:@"groupAnnimation"];
    // // self.pointLayer.transform = CATransform3DMakeRotation(angle2, 0, 0, 1);
}

-(void)setValue:(float)value{<!-- -->
    if(value==-1)
        _valueLable.text=@"--";
    else
    _valueLable.text=[NSString stringWithFormat:@"%0.2f",value];
    
    
    if(value<1.0){<!-- -->
        value=1.0;
    }else if(value>3.0){<!-- -->
        value=3.0f;
    }
// if(value == _value){<!-- -->
// return;
// }
    //Subtract the minimum value and obtain the change value
    float valueTmp1=_value-_minValue;
    float valueTmp2=value-_minValue;

    
    float angle1 = toRad(-135) + valueTmp1*toRad(135);
    float angle2 = toRad(-135) + valueTmp2*toRad(135);
    self.pointLayer.transform = CATransform3DMakeRotation(angle2, 0, 0, 1);
    CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"transform.rotation"];
    
    animation.mass = 10.0; //Mass, affects the spring inertia when the layer is moving. The greater the mass, the greater the extent of spring stretching and compression.
    animation.stiffness = 3000; //Stiffness coefficient (stiffness coefficient/elastic coefficient), the greater the stiffness coefficient, the greater the force generated by deformation, and the faster the movement
    animation.damping = 100.0;//Damping coefficient, the coefficient that prevents the spring from expanding and contracting. The greater the damping coefficient, the faster it stops.
    animation.initialVelocity = 5.f;//Initial velocity, the initial velocity of the animation view; when the velocity is a positive number, the velocity direction is consistent with the motion direction, when the velocity is a negative number, the velocity direction is opposite to the motion direction
    
    animation.duration = 1.0;
    animation.fromValue = @(angle1);
    animation.toValue = @(angle2);
    animation.fillMode = kCAFillModeForwards;
    animation.removedOnCompletion = NO;
    animation.autoreverses = NO;
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    animation.repeatCount = 1;
    [self.pointLayer addAnimation:animation forKey:@"groupAnnimation"];
    
    
    
    
    UIColor *rawColor1 = [UIColor kh_colorWithHexString:@"37E5FE"];
    UIColor *rawColor2 = [UIColor kh_colorWithHexString:@"60df5a"];
    UIColor *rawColor3 = [UIColor kh_colorWithHexString:@"efdf1b"];
    UIColor *rawColor4 = [UIColor kh_colorWithHexString:@"efdf1b"];
    UIColor *rawColor5 = [UIColor kh_colorWithHexString:@"f54f0A"];
    
    UIColor *resultColor=[UIColor kh_colorWithHexString:@"37E5FE"];
    if(value<1.5){<!-- -->
        resultColor = [UIColor qmui_colorFromColor:rawColor1 toColor:rawColor2 progress:1-(1.5-value)]; // Key method
    }else if(value<2){<!-- -->
        resultColor = [UIColor qmui_colorFromColor:rawColor2 toColor:rawColor3 progress:1-(2.0-value)]; // Key method
    }else if(value<2.5){<!-- -->
        resultColor = [UIColor qmui_colorFromColor:rawColor3 toColor:rawColor4 progress:1-(2.5-value)]; // Key method
    }else if(value<=3.0){<!-- -->
        resultColor = [UIColor qmui_colorFromColor:rawColor4 toColor:rawColor5 progress:1-(3.0-value)]; // Key method
    }
   
    self.pointLayer.fillColor=resultColor.CGColor;
    self.bPointLayer.fillColor=resultColor.CGColor;
    UIBezierPath *outArc=[UIBezierPath bezierPathWithArcCenter:_dotCenter radius:_radius-3 startAngle:_startAngle endAngle:_endAngle clockwise:YES];
    _arcLayer.path=outArc.CGPath;
    
    CASpringAnimation *animation1 = [CASpringAnimation animationWithKeyPath:@"strokeEnd"];
    
    
    animation1.mass = 10.0; //Mass, affects the spring inertia when the layer is moving. The greater the mass, the greater the extent of spring stretching and compression.
    animation1.stiffness = 3000; //Stiffness coefficient (stiffness coefficient/elastic coefficient), the greater the stiffness coefficient, the greater the force generated by deformation, and the faster the movement
    animation1.damping = 100.0;//Damping coefficient, the coefficient that prevents the spring from expanding and contracting. The greater the damping coefficient, the faster it stops.
    animation1.initialVelocity = 5.f;//Initial velocity, the initial velocity of the animation view; when the velocity is a positive number, the velocity direction is consistent with the motion direction, when the velocity is a negative number, the velocity direction is opposite to the motion direction
    
    animation1.duration = 1.0;
    animation1.fromValue = @((_value-_minValue)/(_maxValue-_minValue));
    animation1.toValue = @((value-_minValue)/(_maxValue-_minValue));
    animation1.fillMode = kCAFillModeForwards;
    animation1.removedOnCompletion = NO;
    animation1.autoreverses = NO;
    animation1.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    animation1.repeatCount = 1;
    [_arcLayer addAnimation:animation1 forKey:@"groupAnnimation"];
    
    _value=value;
}


@end

use

float w=200;
 WBBDashBoard *dashBoard=[[WBBDashBoard alloc]initWithFrame:CGRectMake(0,10, w, w)];
 dashBoard.center= contextView.center;
[contextView addSubview:dashBoard];

I also have the Android version: