开源 Objective-C IOS 应用开发(二十二)自定义控件--车速仪表盘

文章的目的为了记录使用Objective-C 进行IOS app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 Objective-C IOS 应用开发(一)macOS 的使用

开源 Objective-C IOS 应用开发(二)Xcode安装

开源 Objective-C IOS 应用开发(三)第一个iPhone的APP

开源 Objective-C IOS 应用开发(四)Xcode工程文件结构

开源 Objective-C IOS 应用开发(五)iOS操作(action)和输出口(Outlet)

开源 Objective-C IOS 应用开发(六)Objective-C 和 C语言

开源 Objective-C IOS 应用开发(七)Objective-C核心代码示例

开源 Objective-C IOS 应用开发(八)常见控件UI

开源 Objective-C IOS 应用开发(九)复杂控件-tableview

开源 Objective-C IOS 应用开发(十)数据持久化--文件

开源 Objective-C IOS 应用开发(十一)数据持久化--sqlite

开源 Objective-C IOS 应用开发(十二)通讯--ble

开源 Objective-C IOS 应用开发(十三)通讯--Http访问

开源 Objective-C IOS 应用开发(十四)传感器--陀螺仪和gps

开源 Objective-C IOS 应用开发(十五)通讯--蓝牙ble扫描

开源 Objective-C IOS 应用开发(十六)Storyboard模式下的纯代码界面

开源 Objective-C IOS 应用开发(十七)CAF音频的录制

开源 Objective-C IOS 应用开发(十八)音频的播放

开源 Objective-C IOS 应用开发(十九)视频的播放

开源 Objective-C IOS 应用开发(二十)多线程处理

开源 Objective-C IOS 应用开发(二十一)自定义控件--示波器

开源 Objective-C IOS 应用开发(二十二)自定义控件--车速仪表盘

推荐链接:

开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客

开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客

开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客

开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客

开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客

开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客

开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客

开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客

开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客

开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客

开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客

开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客

开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘-CSDN博客

开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客

开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载-CSDN博客

开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器-CSDN博客

推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

本章内容主要是iphone自定义控件汽车仪表盘的实现。

目录:

1.手机演示

2.所有源码

3.源码分析

一、手机演示

二、所有源码

AppDelegate.h文件

复制代码
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

AppDelegate.m文件

复制代码
#import "AppDelegate.h"
#import "MainViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 创建窗口
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    
    // 创建主视图控制器
    MainViewController *mainVC = [[MainViewController alloc] init];
    
    // 设置窗口的根视图控制器
    self.window.rootViewController = mainVC;
    
    // 显示窗口
    [self.window makeKeyAndVisible];
    
    return YES;
}

// 其他已有的方法保持不变...
@end

SpeedometerView.h文件

复制代码
//
//  SpeedometerView.h
//  dashboard
//
//  Created by Mixic2025 on 2025/11/20.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SpeedometerView : UIView

@property (nonatomic, assign) CGFloat currentSpeed;
@property (nonatomic, assign) CGFloat maxSpeed;
@property (nonatomic, strong) UIColor *needleColor;
@property (nonatomic, strong) UIColor *speedTextColor;

- (void)setSpeed:(CGFloat)speed animated:(BOOL)animated;
- (instancetype)initWithFrame:(CGRect)frame maxSpeed:(CGFloat)maxSpeed;

@end

NS_ASSUME_NONNULL_END

SpeedometerView.m文件

复制代码
#import "SpeedometerView.h"

@interface SpeedometerView ()

@property (nonatomic, strong) CAShapeLayer *backgroundLayer;
@property (nonatomic, strong) CAShapeLayer *speedArcLayer;
@property (nonatomic, strong) CAShapeLayer *needleLayer;
@property (nonatomic, strong) CAGradientLayer *centerCircleLayer;
@property (nonatomic, strong) CAGradientLayer *outerGlowLayer; // 外发光效果
@property (nonatomic, strong) UILabel *speedLabel;
@property (nonatomic, strong) UILabel *unitLabel;
@property (nonatomic, assign) CGFloat startAngle;
@property (nonatomic, assign) CGFloat endAngle;
@property (nonatomic, assign) BOOL isViewSetup;

@end

@implementation SpeedometerView

- (instancetype)initWithFrame:(CGRect)frame maxSpeed:(CGFloat)maxSpeed {
    self = [super initWithFrame:frame];
    if (self) {
        _maxSpeed = maxSpeed;
        _currentSpeed = 0.0;
        _startAngle = M_PI * 0.8;
        _endAngle = M_PI * 2.2;
        _isViewSetup = NO;
        
        // 使用深空蓝作为背景,营造科幻感
        self.backgroundColor = [UIColor colorWithRed:8.0/255.0 green:12.0/255.0 blue:28.0/255.0 alpha:1.0];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    if (!_isViewSetup) {
        [self setupView];
        _isViewSetup = YES;
    } else {
        [self updateViewLayout];
    }
}

- (void)setupView {
    [self.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
    [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    
    // 创建外发光效果
    [self createOuterGlow];
    [self createBackgroundArc];
    [self createSpeedArc];
    [self createCenterCircle];
    [self createNeedle];
    [self createSpeedLabel];
    [self createScaleMarks];
    
    [self setSpeed:_currentSpeed animated:NO];
}

- (void)updateViewLayout {
    [self updateOuterGlow];
    [self updateBackgroundArc];
    [self updateSpeedArc];
    [self updateCenterCircle];
    [self updateNeedle];
    [self updateSpeedLabel];
    [self updateScaleMarks];
}

- (void)createOuterGlow {
    self.outerGlowLayer = [CAGradientLayer layer];
    [self updateOuterGlow];
    
    // 外发光渐变色 - 科幻蓝紫光晕
    UIColor *outerColor = [UIColor colorWithRed:100.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.3];
    UIColor *innerColor = [UIColor colorWithRed:50.0/255.0 green:100.0/255.0 blue:200.0/255.0 alpha:0.1];
    UIColor *clearColor = [UIColor clearColor];
    
    self.outerGlowLayer.colors = @[
        (__bridge id)clearColor.CGColor,
        (__bridge id)innerColor.CGColor,
        (__bridge id)outerColor.CGColor,
        (__bridge id)clearColor.CGColor
    ];
    
    self.outerGlowLayer.startPoint = CGPointMake(0.5, 0.5);
    self.outerGlowLayer.endPoint = CGPointMake(1.0, 1.0);
    self.outerGlowLayer.type = kCAGradientLayerRadial;
    
    [self.layer addSublayer:self.outerGlowLayer];
}

- (void)updateOuterGlow {
    if (!self.outerGlowLayer) return;
    
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height) * 0.45;
    
    self.outerGlowLayer.frame = CGRectMake(centerX - radius, centerY - radius, radius * 2, radius * 2);
}

- (void)createBackgroundArc {
    self.backgroundLayer = [CAShapeLayer layer];
    [self updateBackgroundArc];
    self.backgroundLayer.lineWidth = 18.0;
    
    // 外圆弧颜色 - 半透明科技蓝
    UIColor *backgroundArcColor = [UIColor colorWithRed:64.0/255.0 green:156.0/255.0 blue:255.0/255.0 alpha:0.2];
    self.backgroundLayer.strokeColor = backgroundArcColor.CGColor;
    self.backgroundLayer.fillColor = [UIColor clearColor].CGColor;
    self.backgroundLayer.lineCap = kCALineCapButt;
    
    // 添加发光效果
    self.backgroundLayer.shadowColor = [UIColor colorWithRed:64.0/255.0 green:156.0/255.0 blue:255.0/255.0 alpha:0.5].CGColor;
    self.backgroundLayer.shadowRadius = 3.0;
    self.backgroundLayer.shadowOpacity = 0.8;
    self.backgroundLayer.shadowOffset = CGSizeZero;
    
    [self.layer addSublayer:self.backgroundLayer];
}

- (void)updateBackgroundArc {
    if (!self.backgroundLayer) return;
    
    UIBezierPath *arcPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds))
                                                           radius:MIN(self.bounds.size.width, self.bounds.size.height) * 0.4
                                                       startAngle:self.startAngle
                                                         endAngle:self.endAngle
                                                        clockwise:YES];
    self.backgroundLayer.path = arcPath.CGPath;
}

- (void)createSpeedArc {
    self.speedArcLayer = [CAShapeLayer layer];
    [self updateSpeedArc];
    self.speedArcLayer.lineWidth = 18.0;
    
    // 速度圆弧颜色 - 清新青蓝渐变
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.bounds;
    
    UIColor *startColor = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0]; // 青色
    UIColor *endColor = [UIColor colorWithRed:0.0/255.0 green:150.0/255.0 blue:255.0/255.0 alpha:1.0];   // 蓝色
    
    gradientLayer.colors = @[
        (__bridge id)startColor.CGColor,
        (__bridge id)endColor.CGColor
    ];
    gradientLayer.startPoint = CGPointMake(0, 0.5);
    gradientLayer.endPoint = CGPointMake(1, 0.5);
    
    // 使用速度圆弧作为遮罩
    gradientLayer.mask = self.speedArcLayer;
    
    // 添加发光效果
    gradientLayer.shadowColor = [UIColor colorWithRed:0.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.8].CGColor;
    gradientLayer.shadowRadius = 6.0;
    gradientLayer.shadowOpacity = 1.0;
    gradientLayer.shadowOffset = CGSizeZero;
    
    [self.layer addSublayer:gradientLayer];
    
    // 保存对速度圆弧图层的引用
    self.speedArcLayer.strokeColor = [UIColor whiteColor].CGColor; // 遮罩需要不透明颜色
    self.speedArcLayer.fillColor = [UIColor clearColor].CGColor;
    self.speedArcLayer.lineCap = kCALineCapButt;
    self.speedArcLayer.strokeEnd = 0;
}

- (void)updateSpeedArc {
    if (!self.speedArcLayer) return;
    
    UIBezierPath *arcPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds))
                                                           radius:MIN(self.bounds.size.width, self.bounds.size.height) * 0.4
                                                       startAngle:self.startAngle
                                                         endAngle:self.endAngle
                                                        clockwise:YES];
    self.speedArcLayer.path = arcPath.CGPath;
}

- (void)createCenterCircle {
    self.centerCircleLayer = [CAGradientLayer layer];
    [self updateCenterCircle];
    
    // 中心圆渐变色 - 深空紫到宇宙蓝
    UIColor *centerColor1 = [UIColor colorWithRed:25.0/255.0 green:10.0/255.0 blue:80.0/255.0 alpha:1.0];
    UIColor *centerColor2 = [UIColor colorWithRed:10.0/255.0 green:30.0/255.0 blue:120.0/255.0 alpha:1.0];
    UIColor *centerColor3 = [UIColor colorWithRed:5.0/255.0 green:15.0/255.0 blue:60.0/255.0 alpha:1.0];
    
    self.centerCircleLayer.colors = @[
        (__bridge id)centerColor1.CGColor,
        (__bridge id)centerColor2.CGColor,
        (__bridge id)centerColor3.CGColor
    ];
    
    self.centerCircleLayer.startPoint = CGPointMake(0.2, 0.2);
    self.centerCircleLayer.endPoint = CGPointMake(0.8, 0.8);
    self.centerCircleLayer.locations = @[@0.0, @0.5, @1.0];
    
    // 添加内发光效果
    self.centerCircleLayer.shadowColor = [UIColor colorWithRed:100.0/255.0 green:100.0/255.0 blue:255.0/255.0 alpha:0.5].CGColor;
    self.centerCircleLayer.shadowRadius = 10.0;
    self.centerCircleLayer.shadowOpacity = 0.6;
    self.centerCircleLayer.shadowOffset = CGSizeZero;
    
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height) * 0.4;
    CGFloat circleRadius = radius * (2.0/5.0);
    
    maskLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, circleRadius * 2, circleRadius * 2)].CGPath;
    self.centerCircleLayer.mask = maskLayer;
    
    [self.layer addSublayer:self.centerCircleLayer];
}

- (void)updateCenterCircle {
    if (!self.centerCircleLayer) return;
    
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height) * 0.4;
    CGFloat circleRadius = radius * (2.0/5.0);
    
    self.centerCircleLayer.frame = CGRectMake(centerX - circleRadius, centerY - circleRadius, circleRadius * 2, circleRadius * 2);
    
    CAShapeLayer *maskLayer = (CAShapeLayer *)self.centerCircleLayer.mask;
    if (maskLayer) {
        maskLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, circleRadius * 2, circleRadius * 2)].CGPath;
    }
}

- (void)createNeedle {
    self.needleLayer = [CAShapeLayer layer];
    [self updateNeedle];
    self.needleLayer.lineWidth = 4.0;
    
    // 指针颜色 - 荧光青
    UIColor *needleColor = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:200.0/255.0 alpha:1.0];
    self.needleLayer.strokeColor = needleColor.CGColor;
    self.needleLayer.fillColor = [UIColor clearColor].CGColor;
    
    // 指针发光效果
    self.needleLayer.shadowColor = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:200.0/255.0 alpha:0.8].CGColor;
    self.needleLayer.shadowRadius = 4.0;
    self.needleLayer.shadowOpacity = 1.0;
    self.needleLayer.shadowOffset = CGSizeZero;
    
    [self.layer addSublayer:self.needleLayer];
}

- (void)updateNeedle {
    if (!self.needleLayer) return;
    
    CGFloat currentAngle = self.startAngle + (self.endAngle - self.startAngle) * (self.currentSpeed / self.maxSpeed);
    
    UIBezierPath *needlePath = [UIBezierPath bezierPath];
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height) * 0.4;
    CGFloat centerCircleRadius = radius * (2.0/5.0);
    
    CGFloat startX = centerX + radius * cos(currentAngle);
    CGFloat startY = centerY + radius * sin(currentAngle);
    CGFloat endX = centerX + centerCircleRadius * cos(currentAngle);
    CGFloat endY = centerY + centerCircleRadius * sin(currentAngle);
    
    [needlePath moveToPoint:CGPointMake(startX, startY)];
    [needlePath addLineToPoint:CGPointMake(endX, endY)];
    
    self.needleLayer.path = needlePath.CGPath;
}

- (void)createSpeedLabel {
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    
    self.speedLabel = [[UILabel alloc] initWithFrame:CGRectMake(centerX - 80, centerY - 25, 160, 50)];
    self.speedLabel.text = @"0";
    
    // 速度文字颜色 - 荧光白
    self.speedLabel.textColor = [UIColor colorWithRed:220.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0];
    self.speedLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:42];
    self.speedLabel.textAlignment = NSTextAlignmentCenter;
    
    // 文字发光效果
    self.speedLabel.layer.shadowColor = [UIColor colorWithRed:100.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.6].CGColor;
    self.speedLabel.layer.shadowRadius = 3.0;
    self.speedLabel.layer.shadowOpacity = 1.0;
    self.speedLabel.layer.shadowOffset = CGSizeZero;
    
    [self addSubview:self.speedLabel];
    
    self.unitLabel = [[UILabel alloc] initWithFrame:CGRectMake(centerX - 40, centerY + 30, 80, 20)];
    self.unitLabel.text = @"km/h";
    self.unitLabel.textColor = [UIColor colorWithRed:150.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.8];
    self.unitLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:14];
    self.unitLabel.textAlignment = NSTextAlignmentCenter;
    [self addSubview:self.unitLabel];
}

- (void)updateSpeedLabel {
    if (!self.speedLabel || !self.unitLabel) return;
    
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    
    self.speedLabel.frame = CGRectMake(centerX - 80, centerY - 25, 160, 50);
    self.unitLabel.frame = CGRectMake(centerX - 40, centerY + 30, 80, 20);
}

- (void)createScaleMarks {
    CGFloat centerX = CGRectGetMidX(self.bounds);
    CGFloat centerY = CGRectGetMidY(self.bounds);
    CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height) * 0.4;
    
    for (int i = 0; i <= 10; i++) {
        CGFloat value = self.maxSpeed * i / 10.0;
        CGFloat angle = self.startAngle + (self.endAngle - self.startAngle) * (value / self.maxSpeed);
        
        CAShapeLayer *scaleLayer = [CAShapeLayer layer];
        UIBezierPath *scalePath = [UIBezierPath bezierPath];
        
        CGFloat innerRadius = radius - 25;
        CGFloat outerRadius = radius - (i % 5 == 0 ? 8 : 12);
        
        [scalePath moveToPoint:CGPointMake(centerX + innerRadius * cos(angle),
                                         centerY + innerRadius * sin(angle))];
        [scalePath addLineToPoint:CGPointMake(centerX + outerRadius * cos(angle),
                                            centerY + outerRadius * sin(angle))];
        
        scaleLayer.path = scalePath.CGPath;
        scaleLayer.lineWidth = i % 5 == 0 ? 3.0 : 1.5;
        
        // 刻度颜色 - 淡蓝色
        scaleLayer.strokeColor = [UIColor colorWithRed:100.0/255.0 green:180.0/255.0 blue:255.0/255.0 alpha:0.8].CGColor;
        
        // 刻度发光效果
        if (i % 5 == 0) {
            scaleLayer.shadowColor = [UIColor colorWithRed:100.0/255.0 green:180.0/255.0 blue:255.0/255.0 alpha:0.5].CGColor;
            scaleLayer.shadowRadius = 2.0;
            scaleLayer.shadowOpacity = 1.0;
        }
        
        [self.layer addSublayer:scaleLayer];
        
        if (i % 5 == 0) {
            UILabel *scaleLabel = [[UILabel alloc] init];
            scaleLabel.text = [NSString stringWithFormat:@"%.0f", value];
            scaleLabel.textColor = [UIColor colorWithRed:150.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.9];
            scaleLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:12];
            scaleLabel.textAlignment = NSTextAlignmentCenter;
            [scaleLabel sizeToFit];
            
            CGFloat labelRadius = radius - 42;
            CGFloat labelX = centerX + labelRadius * cos(angle) - scaleLabel.bounds.size.width / 2;
            CGFloat labelY = centerY + labelRadius * sin(angle) - scaleLabel.bounds.size.height / 2;
            
            scaleLabel.frame = CGRectMake(labelX, labelY, scaleLabel.bounds.size.width, scaleLabel.bounds.size.height);
            [self addSubview:scaleLabel];
        }
    }
}

- (void)updateScaleMarks {
    for (CALayer *layer in self.layer.sublayers) {
        if ([layer isKindOfClass:[CAShapeLayer class]] && layer != self.backgroundLayer && layer != self.speedArcLayer && layer != self.needleLayer) {
            [layer removeFromSuperlayer];
        }
    }
    
    for (UIView *view in self.subviews) {
        if (view != self.speedLabel && view != self.unitLabel) {
            [view removeFromSuperview];
        }
    }
    
    [self createScaleMarks];
}

- (void)setSpeed:(CGFloat)speed animated:(BOOL)animated {
    speed = MAX(0, MIN(speed, self.maxSpeed));
    
    if (!_isViewSetup) {
        _currentSpeed = speed;
        return;
    }
    
    _currentSpeed = speed;
    self.speedLabel.text = [NSString stringWithFormat:@"%.0f", speed];
    self.speedArcLayer.strokeEnd = speed / self.maxSpeed;
    [self updateNeedle];
}

- (void)setCurrentSpeed:(CGFloat)currentSpeed {
    _currentSpeed = currentSpeed;
    if (_isViewSetup) {
        [self setSpeed:currentSpeed animated:NO];
    }
}

@end

MainViewController.h文件

复制代码
//
//  MainViewController.h
//  dashboard
//
//  Created by Mixic2025 on 2025/11/20.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface MainViewController : UIViewController

@end

NS_ASSUME_NONNULL_END

MainViewController.m文件

复制代码
//
//  MainViewController.m
//  dashboard
//
//  Created by Mixic2025 on 2025/11/20.
//

#import "MainViewController.h"
#import "SpeedometerView.h"

@interface MainViewController ()

@property (nonatomic, strong) SpeedometerView *speedometer;
@property (nonatomic, strong) UIButton *accelerateButton;
@property (nonatomic, strong) UIButton *decelerateButton;
@property (nonatomic, strong) NSTimer *speedTimer;
@property (nonatomic, assign) CGFloat targetSpeed;

@end

@implementation MainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor blackColor];
    self.targetSpeed = 0.0;
    
    [self setupSpeedometer];
    [self setupControlButtons];
}

- (void)setupSpeedometer {
    // 创建速度表 - 居中显示,大小为300x300
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat speedometerSize = 300;
    CGFloat x = (screenWidth - speedometerSize) / 2;
    
    self.speedometer = [[SpeedometerView alloc] initWithFrame:CGRectMake(x, 100, speedometerSize, speedometerSize) maxSpeed:240];
    [self.view addSubview:self.speedometer];
    
    // 延迟设置初始速度,确保视图已布局完成
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.speedometer setSpeed:0 animated:YES];
    });
}

- (void)setupControlButtons {
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat buttonWidth = 120;
    CGFloat buttonHeight = 50;
    CGFloat spacing = 20;
    CGFloat totalWidth = buttonWidth * 2 + spacing;
    CGFloat startX = (screenWidth - totalWidth) / 2;
    CGFloat y = CGRectGetMaxY(self.speedometer.frame) + 80;
    
    // 加速按钮
    self.accelerateButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.accelerateButton.frame = CGRectMake(startX, y, buttonWidth, buttonHeight);
    [self.accelerateButton setTitle:@"加速 +" forState:UIControlStateNormal];
    [self.accelerateButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    self.accelerateButton.backgroundColor = [UIColor systemGreenColor];
    self.accelerateButton.layer.cornerRadius = 10;
    self.accelerateButton.titleLabel.font = [UIFont boldSystemFontOfSize:18];
    [self.accelerateButton addTarget:self action:@selector(startAccelerating) forControlEvents:UIControlEventTouchDown];
    [self.accelerateButton addTarget:self action:@selector(stopChangingSpeed) forControlEvents:UIControlEventTouchUpInside];
    [self.accelerateButton addTarget:self action:@selector(stopChangingSpeed) forControlEvents:UIControlEventTouchUpOutside];
    [self.view addSubview:self.accelerateButton];
    
    // 减速按钮
    self.decelerateButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.decelerateButton.frame = CGRectMake(startX + buttonWidth + spacing, y, buttonWidth, buttonHeight);
    [self.decelerateButton setTitle:@"减速 -" forState:UIControlStateNormal];
    [self.decelerateButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    self.decelerateButton.backgroundColor = [UIColor systemRedColor];
    self.decelerateButton.layer.cornerRadius = 10;
    self.decelerateButton.titleLabel.font = [UIFont boldSystemFontOfSize:18];
    [self.decelerateButton addTarget:self action:@selector(startDecelerating) forControlEvents:UIControlEventTouchDown];
    [self.decelerateButton addTarget:self action:@selector(stopChangingSpeed) forControlEvents:UIControlEventTouchUpInside];
    [self.decelerateButton addTarget:self action:@selector(stopChangingSpeed) forControlEvents:UIControlEventTouchUpOutside];
    [self.view addSubview:self.decelerateButton];
}

- (void)startAccelerating {
    [self.speedTimer invalidate];
    self.speedTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(increaseSpeed) userInfo:nil repeats:YES];
}

- (void)startDecelerating {
    [self.speedTimer invalidate];
    self.speedTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(decreaseSpeed) userInfo:nil repeats:YES];
}

- (void)stopChangingSpeed {
    [self.speedTimer invalidate];
    self.speedTimer = nil;
}

- (void)increaseSpeed {
    CGFloat currentSpeed = self.speedometer.currentSpeed;
    if (currentSpeed < self.speedometer.maxSpeed) {
        CGFloat newSpeed = currentSpeed + 2.0;
        [self.speedometer setSpeed:newSpeed animated:YES];
    }
}

- (void)decreaseSpeed {
    CGFloat currentSpeed = self.speedometer.currentSpeed;
    if (currentSpeed > 0) {
        CGFloat newSpeed = currentSpeed - 2.0;
        [self.speedometer setSpeed:newSpeed animated:YES];
    }
}

- (void)dealloc {
    [self.speedTimer invalidate];
    self.speedTimer = nil;
}

@end

info.plist中删除原来的mainifest配置

三、源码分析

  1. AppDelegate.h

    #import <UIKit/UIKit.h>

    @interface AppDelegate : UIResponder <UIApplicationDelegate>

    @property (strong, nonatomic) UIWindow *window;

    @end

功能:应用委托的头文件,声明窗口属性。

  1. AppDelegate.m

    • (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

      // 创建窗口
      self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

      // 创建主视图控制器
      MainViewController *mainVC = [[MainViewController alloc] init];

      // 设置窗口的根视图控制器
      self.window.rootViewController = mainVC;

      // 显示窗口
      [self.window makeKeyAndVisible];

      return YES;
      }

功能:应用启动入口,创建窗口和主视图控制器。

  1. SpeedometerView.h

    @interface SpeedometerView : UIView

    @property (nonatomic, assign) CGFloat currentSpeed;
    @property (nonatomic, assign) CGFloat maxSpeed;
    @property (nonatomic, strong) UIColor *needleColor;
    @property (nonatomic, strong) UIColor *speedTextColor;

    • (void)setSpeed:(CGFloat)speed animated:(BOOL)animated;
    • (instancetype)initWithFrame:(CGRect)frame maxSpeed:(CGFloat)maxSpeed;

    @end

功能:仪表盘视图的公共接口,定义属性和方法。

  1. SpeedometerView.m - 核心类

4.1 初始化方法

复制代码
- (instancetype)initWithFrame:(CGRect)frame maxSpeed:(CGFloat)maxSpeed {
    self = [super initWithFrame:frame];
    if (self) {
        _maxSpeed = maxSpeed;
        _currentSpeed = 0.0;
        _startAngle = M_PI * 0.8;    // 144度
        _endAngle = M_PI * 2.2;      // 396度
        _isViewSetup = NO;

        // 深空蓝背景
        self.backgroundColor = [UIColor colorWithRed:8.0/255.0 green:12.0/255.0 blue:28.0/255.0 alpha:1.0];
    }
    return self;
}

功能:初始化仪表盘参数和背景色。

4.2 布局管理方法

复制代码
- (void)layoutSubviews {
    [super layoutSubviews];

    if (!_isViewSetup) {
        [self setupView];        // 首次创建
        _isViewSetup = YES;
    } else {
        [self updateViewLayout]; // 更新布局
    }
}

功能:自动布局回调,管理视图创建和更新。

复制代码
- (void)setupView {
    [self.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
    [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];

    // 按层次创建所有组件
    [self createOuterGlow];
    [self createBackgroundArc];
    [self createSpeedArc];
    [self createCenterCircle];
    [self createNeedle];
    [self createSpeedLabel];
    [self createScaleMarks];

    [self setSpeed:_currentSpeed animated:NO];
}

功能:首次设置所有UI组件。

复制代码
- (void)updateViewLayout {
    [self updateOuterGlow];
    [self updateBackgroundArc];
    [self updateSpeedArc];
    [self updateCenterCircle];
    [self updateNeedle];
    [self updateSpeedLabel];
    [self updateScaleMarks];
}

功能:更新所有组件布局。

4.3 外发光效果方法

复制代码
- (void)createOuterGlow {
    self.outerGlowLayer = [CAGradientLayer layer];
    [self updateOuterGlow];

    // 蓝紫光晕渐变
    UIColor *outerColor = [UIColor colorWithRed:100.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.3];
    UIColor *innerColor = [UIColor colorWithRed:50.0/255.0 green:100.0/255.0 blue:200.0/255.0 alpha:0.1];

    self.outerGlowLayer.colors = @[clearColor, innerColor, outerColor, clearColor];
    self.outerGlowLayer.type = kCAGradientLayerRadial; // 径向渐变
    [self.layer addSublayer:self.outerGlowLayer];
}

功能:创建仪表盘外圈的发光光晕效果。

4.4 背景圆弧方法

复制代码
- (void)createBackgroundArc {
    self.backgroundLayer = [CAShapeLayer layer];
    [self updateBackgroundArc];
    self.backgroundLayer.lineWidth = 18.0;

    // 半透明科技蓝
    UIColor *backgroundArcColor = [UIColor colorWithRed:64.0/255.0 green:156.0/255.0 blue:255.0/255.0 alpha:0.2];
    self.backgroundLayer.strokeColor = backgroundArcColor.CGColor;

    // 添加发光效果
    self.backgroundLayer.shadowColor = [UIColor colorWithRed:64.0/255.0 green:156.0/255.0 blue:255.0/255.0 alpha:0.5].CGColor;
    self.backgroundLayer.shadowRadius = 3.0;
    self.backgroundLayer.shadowOpacity = 0.8;
}

功能:创建静态的背景圆弧。

4.5 速度圆弧方法(核心技术)

复制代码
- (void)createSpeedArc {
    self.speedArcLayer = [CAShapeLayer layer];
    [self updateSpeedArc];
    self.speedArcLayer.lineWidth = 18.0;

    // 创建渐变图层
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.bounds;

    // 青蓝渐变
    UIColor *startColor = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0];
    UIColor *endColor = [UIColor colorWithRed:0.0/255.0 green:150.0/255.0 blue:255.0/255.0 alpha:1.0];
    gradientLayer.colors = @[(__bridge id)startColor.CGColor, (__bridge id)endColor.CGColor];

    // 关键:使用速度圆弧作为渐变的遮罩
    gradientLayer.mask = self.speedArcLayer;

    [self.layer addSublayer:gradientLayer];

    self.speedArcLayer.strokeEnd = 0; // 初始长度为0
}

功能:创新性地使用遮罩技术,实现渐变速度圆弧效果。

4.6 中心圆方法

复制代码
- (void)createCenterCircle {
    self.centerCircleLayer = [CAGradientLayer layer];
    [self updateCenterCircle];

    // 深空紫到宇宙蓝的三色渐变
    UIColor *centerColor1 = [UIColor colorWithRed:25.0/255.0 green:10.0/255.0 blue:80.0/255.0 alpha:1.0];
    UIColor *centerColor2 = [UIColor colorWithRed:10.0/255.0 green:30.0/255.0 blue:120.0/255.0 alpha:1.0];
    UIColor *centerColor3 = [UIColor colorWithRed:5.0/255.0 green:15.0/255.0 blue:60.0/255.0 alpha:1.0];

    self.centerCircleLayer.colors = @[centerColor1, centerColor2, centerColor3];
    self.centerCircleLayer.locations = @[@0.0, @0.5, @1.0];

    // 使用圆形遮罩
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.path = [UIBezierPath bezierPathWithOvalInRect:...].CGPath;
    self.centerCircleLayer.mask = maskLayer;
}

功能:创建带渐变和发光效果的中心圆。

4.7 指针方法

复制代码
- (void)createNeedle {
    self.needleLayer = [CAShapeLayer layer];
    [self updateNeedle];
    self.needleLayer.lineWidth = 4.0;

    // 荧光青指针
    UIColor *needleColor = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:200.0/255.0 alpha:1.0];
    self.needleLayer.strokeColor = needleColor.CGColor;

    // 指针发光效果
    self.needleLayer.shadowColor = [UIColor colorWithRed:0.0/255.0 green:255.0/255.0 blue:200.0/255.0 alpha:0.8].CGColor;
    self.needleLayer.shadowRadius = 4.0;
}


- (void)updateNeedle {
    CGFloat currentAngle = self.startAngle + (self.endAngle - self.startAngle) * (self.currentSpeed / self.maxSpeed);

    // 计算起点(圆弧边缘)和终点(中心圆边缘)
    CGFloat startX = centerX + radius * cos(currentAngle);
    CGFloat startY = centerY + radius * sin(currentAngle);
    CGFloat endX = centerX + centerCircleRadius * cos(currentAngle);
    CGFloat endY = centerY + centerCircleRadius * sin(currentAngle);

    // 创建指针路径
    [needlePath moveToPoint:CGPointMake(startX, startY)];
    [needlePath addLineToPoint:CGPointMake(endX, endY)];

    self.needleLayer.path = needlePath.CGPath;
}

功能:创建和更新指针位置。

4.8 标签和刻度方法

复制代码
- (void)createSpeedLabel {
    self.speedLabel = [[UILabel alloc] initWithFrame:...];
    self.speedLabel.text = @"0";

    // 荧光白文字
    self.speedLabel.textColor = [UIColor colorWithRed:220.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1.0];
    self.speedLabel.font = [UIFont fontWithName:@"HelveticaNeue-Light" size:42];

    // 文字发光效果
    self.speedLabel.layer.shadowColor = [UIColor colorWithRed:100.0/255.0 green:200.0/255.0 blue:255.0/255.0 alpha:0.6].CGColor;
    self.speedLabel.layer.shadowRadius = 3.0;
}

功能:创建速度数字和单位标签。

复制代码
- (void)createScaleMarks {
    for (int i = 0; i <= 10; i++) {
        CGFloat value = self.maxSpeed * i / 10.0;
        CGFloat angle = self.startAngle + (self.endAngle - self.startAngle) * (value / self.maxSpeed);

        // 创建刻度线
        CAShapeLayer *scaleLayer = [CAShapeLayer layer];
        // 主刻度加粗,次刻度细
        scaleLayer.lineWidth = i % 5 == 0 ? 3.0 : 1.5;

        // 每5个刻度显示数值
        if (i % 5 == 0) {
            UILabel *scaleLabel = [[UILabel alloc] init];
            scaleLabel.text = [NSString stringWithFormat:@"%.0f", value];
            // 添加标签...
        }
    }
}

功能:创建刻度线和数值标签。

4.9 核心速度控制方法

复制代码
- (void)setSpeed:(CGFloat)speed animated:(BOOL)animated {
    speed = MAX(0, MIN(speed, self.maxSpeed)); // 限制范围

    if (!_isViewSetup) {
        _currentSpeed = speed;
        return;
    }

    _currentSpeed = speed;
    self.speedLabel.text = [NSString stringWithFormat:@"%.0f", speed];
    self.speedArcLayer.strokeEnd = speed / self.maxSpeed; // 关键:控制圆弧长度
    [self updateNeedle]; // 更新指针位置
}

功能:核心方法,设置速度并更新所有相关UI。

  1. MainViewController.m - 主控制器

5.1 视图加载方法

复制代码
- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor blackColor];
    self.targetSpeed = 0.0;

    [self setupSpeedometer];
    [self setupControlButtons];
}

功能:控制器初始化,设置背景色和创建UI。

5.2 仪表盘设置方法

复制代码
- (void)setupSpeedometer {
    // 计算居中位置
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat speedometerSize = 300;
    CGFloat x = (screenWidth - speedometerSize) / 2;

    // 创建仪表盘
    self.speedometer = [[SpeedometerView alloc] initWithFrame:CGRectMake(x, 100, speedometerSize, speedometerSize) maxSpeed:240];
    [self.view addSubview:self.speedometer];

    // 延迟设置初始速度
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.speedometer setSpeed:0 animated:YES];
    });
}

功能:创建并定位仪表盘视图。

5.3 控制按钮设置方法

复制代码
- (void)setupControlButtons {
    // 计算按钮布局
    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat buttonWidth = 120;
    CGFloat buttonHeight = 50;
    CGFloat spacing = 20;
    CGFloat totalWidth = buttonWidth * 2 + spacing;
    CGFloat startX = (screenWidth - totalWidth) / 2;
    CGFloat y = CGRectGetMaxY(self.speedometer.frame) + 80;

    // 创建加速按钮
    self.accelerateButton = [UIButton buttonWithType:UIButtonTypeSystem];
    self.accelerateButton.frame = CGRectMake(startX, y, buttonWidth, buttonHeight);
    [self.accelerateButton setTitle:@"加速 +" forState:UIControlStateNormal];
    self.accelerateButton.backgroundColor = [UIColor systemGreenColor];
    self.accelerateButton.layer.cornerRadius = 10;

    // 绑定触摸事件
    [self.accelerateButton addTarget:self action:@selector(startAccelerating) forControlEvents:UIControlEventTouchDown];
    [self.accelerateButton addTarget:self action:@selector(stopChangingSpeed) forControlEvents:UIControlEventTouchUpInside];

    // 类似创建减速按钮...
}

功能:创建加速和减速按钮,并绑定触摸事件。

5.4 速度控制方法

复制代码
- (void)startAccelerating {
    [self.speedTimer invalidate];
    self.speedTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(increaseSpeed) userInfo:nil repeats:YES];
}

功能:开始加速,启动定时器。

复制代码
- (void)increaseSpeed {
    CGFloat currentSpeed = self.speedometer.currentSpeed;
    if (currentSpeed < self.speedometer.maxSpeed) {
        CGFloat newSpeed = currentSpeed + 2.0;
        [self.speedometer setSpeed:newSpeed animated:YES];
    }
}

功能:定时器回调,每次增加2单位速度。

复制代码
- (void)stopChangingSpeed {
    [self.speedTimer invalidate];
    self.speedTimer = nil;
}

功能:停止速度变化,取消定时器。

项目架构总结:

  1. AppDelegate:应用入口,窗口管理

  2. SpeedometerView:自定义仪表盘组件

  3. MainViewController:主界面控制器,用户交互处理

相关推荐
從南走到北1 小时前
JAVA国际版同城跑腿源码快递代取帮买帮送同城服务源码支持Android+IOS+H5
android·java·ios·微信小程序
X***48962 小时前
JavaWebSocket案例
ios·finebi·view design
2501_915918412 小时前
如何解析iOS崩溃日志:从获取到符号化分析
android·ios·小程序·https·uni-app·iphone·webview
智源研究院官方账号2 小时前
具身开放日点燃生态引擎,智源以开源开放驱动具身智能创新
开源
ajassi20005 小时前
开源 Objective-C IOS 应用开发(二十一)自定义控件--示波器
ios·开源·objective-c
ajassi20005 小时前
开源 Objective-C IOS 应用开发(二十)多线程处理
ios·开源·objective-c
Q***l68715 小时前
后端服务网格可观测性,Jaeger追踪可观测性实践:Jaeger追踪详解
spring cloud·objective-c·p2p
00后程序员张17 小时前
Swift 应用加密工具的全面方案,从源码混淆到 IPA 成品加固的多层安全实践
安全·ios·小程序·uni-app·ssh·iphone·swift
坚果派·白晓明19 小时前
开源鸿蒙终端工具Termony构建HNP包指导手册Mac版
macos·开源·harmonyos