iOS Tutorial: Custom Speedometer Control

March 6, 2012


Speedometer, Gauge, Speedometer Dial

Step 1 –Start Xcode and create a View based application with name “SpeedometerDemo”.

Step 2 —- Open “SpeedometerDemoViewController.h” and put the following code in it.

@interface SpeedometerDemoViewController : UIViewController {
 UIImageView *needleImageView;
 float speedometerCurrentValue;
 float prevAngleFactor;
 float angle;
 NSTimer *speedometer_Timer;
 UILabel *speedometerReading;
 NSString *maxVal;

 @property(nonatomic,retain) UIImageView *needleImageView;
 @property(nonatomic,assign) float speedometerCurrentValue;
 @property(nonatomic,assign) float prevAngleFactor;
 @property(nonatomic,assign) float angle;
 @property(nonatomic,retain) NSTimer *speedometer_Timer;
 @property(nonatomic,retain) UILabel *speedometerReading;
 @property(nonatomic,retain) NSString *maxVal;

-(void) addMeterViewContents;
-(void) rotateIt:(float)angl;
-(void) rotateNeedle;
-(void) setSpeedometerCurrentValue;
-(void) calculateDeviationAngle;

The methods declared are:

addMeterViewContents – This method is used to add view contents.

rotateIt:(float)angl – This method is used the set the needle in default position which is 0;

rotateNeedle – This method rotates the needle.

setSpeedometerCurrentValue – This method generates random value by which the needle should rotate.

calculateDeviationAngle – This method calculates the angle of deviation by which the needle should rotate.

Put the following code in the “SliderDemoViewController.m” file.

@implementation SpeedometerDemoViewController
 @synthesize needleImageView;
 @synthesize speedometerCurrentValue;
 @synthesize prevAngleFactor;
 @synthesize angle;
 @synthesize speedometer_Timer;
 @synthesize speedometerReading;
 @synthesize maxVal;

 - (void)viewDidLoad {

// Add Meter Contents //
 [self addMeterViewContents];

[super viewDidLoad];
 - (void)dealloc {
 [maxVal release];
 [needleImageView release];
 [speedometer_Timer release];
 [speedometerReading release];
 [super dealloc];

#pragma mark -
#pragma mark addMeterViewContents Methods

-(void) addMeterViewContents

UIImageView *backgroundImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 320,460)];
 backgroundImageView.image = [UIImage imageNamed:@"main_bg.png"];
 [self.view addSubview:backgroundImageView];
 [backgroundImageView release];

UIImageView *meterImageView = [[UIImageView alloc]initWithFrame:CGRectMake(10, 40, 286,315)];
 meterImageView.image = [UIImage imageNamed:@"meter.png"];
 [self.view addSubview:meterImageView];
 [meterImageView release];

//  Needle //
 UIImageView *imgNeedle = [[UIImageView alloc]initWithFrame:CGRectMake(143,155, 22, 84)];
 self.needleImageView = imgNeedle;
 [imgNeedle release];

self.needleImageView.layer.anchorPoint = CGPointMake(self.needleImageView.layer.anchorPoint.x, self.needleImageView.layer.anchorPoint.y*2);  // Shift the Needle center point to one of the end points of the needle image.
 self.needleImageView.backgroundColor = [UIColor clearColor];
 self.needleImageView.image = [UIImage imageNamed:@"arrow.png"];
 [self.view addSubview:self.needleImageView];

// Needle Dot //
 UIImageView *meterImageViewDot = [[UIImageView alloc]initWithFrame:CGRectMake(131.5, 175, 45,44)];
 meterImageViewDot.image = [UIImage imageNamed:@"center_wheel.png"];
 [self.view addSubview:meterImageViewDot];
 [meterImageViewDot release];

// Speedometer Reading //
 UILabel *tempReading = [[UILabel alloc] initWithFrame:CGRectMake(125, 250, 60, 30)];
 self.speedometerReading = tempReading;
 [tempReading release];
 self.speedometerReading.textAlignment = UITextAlignmentCenter;
 self.speedometerReading.backgroundColor = [UIColor blackColor];
 self.speedometerReading.text= @"0";
 self.speedometerReading.textColor = [UIColor colorWithRed:114/255.f green:146/255.f blue:38/255.f alpha:1.0];
 [self.view addSubview:self.speedometerReading ];

// Set Max Value //
 self.maxVal = @"100";

/// Set Needle pointer initialy at zero //
 [self rotateIt:-118.4];   // Set the needle pointer initially at zero //

// Set previous angle //
 self.prevAngleFactor = -118.4;  // To keep track of previous deviated angle //

// Set Speedometer Value //
 [self setSpeedometerCurrentValue];

#pragma mark -
 #pragma mark calculateDeviationAngle Method

-(void) calculateDeviationAngle

if([self.maxVal floatValue]>0)
 self.angle = ((self.speedometerCurrentValue *237.4)/[self.maxVal floatValue])-118.4;  // 237.4 - Total angle between 0 - 100 //
 self.angle = 0;

 self.angle = -118.4;
 if(self.angle>=119)  // 119 deg is the angle value for 100
 self.angle = 119;

// If Calculated angle is greater than 180 deg, to avoid the needle to rotate in reverse direction first rotate the needle 1/3 of the calculated angle and then 2/3. //
 if(abs(self.angle-self.prevAngleFactor) >180)
 [UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.5f];
 [self rotateIt:self.angle/3];
 [UIView commitAnimations];

[UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.5f];
 [self rotateIt:(self.angle*2)/3];
 [UIView commitAnimations];

 self.prevAngleFactor = self.angle;

// Rotate Needle //
 [self rotateNeedle];


#pragma mark -
 #pragma mark rotateNeedle Method
 -(void) rotateNeedle
 [UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.5f];
 [self.needleImageView setTransform: CGAffineTransformMakeRotation((M_PI / 180) * self.angle)];
 [UIView commitAnimations];


#pragma mark -
 #pragma mark setSpeedometerCurrentValue

-(void) setSpeedometerCurrentValue
 [self.speedometer_Timer invalidate];
 self.speedometer_Timer = nil;
 self.speedometerCurrentValue =  arc4random() % 100; // Generate Random value between 0 to 100. //

self.speedometer_Timer = [NSTimer  scheduledTimerWithTimeInterval:2 target:self selector:@selector(setSpeedometerCurrentValue) userInfo:nil repeats:YES];

self.speedometerReading.text = [NSString stringWithFormat:@"%.2f",self.speedometerCurrentValue];

// Calculate the Angle by which the needle should rotate //
 [self calculateDeviationAngle];
 #pragma mark -
 #pragma mark Speedometer needle Rotation View Methods

-(void) rotateIt:(float)angl
 [UIView beginAnimations:nil context:nil];
 [UIView setAnimationDuration:0.01f];

[self.needleImageView setTransform: CGAffineTransformMakeRotation((M_PI / 180) *angl)];

[UIView commitAnimations];

addMeterViewContents – In this method we set and add different images like needle,speedometer etc, with respect to the view frame.
In this method we also move the needle center to one of its end points in order to rotate a needle by a well defined axis and for this we set its anchor points y co-ordinate twice its current y co-ordinate.

rotateIt:(float)angl – In this method first we find min and max angle by addHoc process and then set the needle in default position which is 0;

calculateDeviationAngle – In this method we calculate the angle of deviation by which the needle should rotate.

rotateNeedle – In this method we rotate the needle by the calculate angle of deviation.

setSpeedometerCurrentValue – In this method we generate random value between 0 and 100 using arc4random function.

Step 3: Save,build and run the project. Now you can see custom speedometer with a moving needle on random generated values, with the value displayed on the label below.

You can download the source code SpeedometerDemo.

  • Parth Patel

    Please give me a code for MAX Speed is 280.
    Here u had provide MAX Speed is 100.
    Can U Give me this type of Code?

  • suresh

    Good one and useful.Here it is given for 180 Degrees max.
    if(abs(self.angle-self.prevAngleFactor) >180). Is there any possibility we can increase to 300? If Yes, where and all we have to change the values.Can u give me example. Thanks in advance.

  • suresh

    100 is not MAX Speed, it is the value(0-100). If u want to change the speed of dialer,decrease the value of setAnimationDuration:

  • dcrescenti

    Great example. Why 118.4 and not 237.4 /2? for the total angle? I use a costant for 180=TOT_DEGREES and TOT_DEGREES/2 in palce of 118.4.

  • suresh

    Here i got the dialer and needle to rotate linearly though the value varies from 0-100. Here there is a condition if the deviation is more than 40,then it jumps to that value instead of animating. Avoid that using CGAffineTransform.

  • Ragul

    Thank you it help me a lot… Could you please guide me to reduce the size of this speedometer….