开源 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:主界面控制器,用户交互处理

相关推荐
Haha_bj2 小时前
Flutter ——flutter_screenutil 屏幕适配
android·ios
冬奇Lab2 小时前
一天一个开源项目(第17篇):ViMax - 多智能体视频生成框架,导演、编剧、制片人全包
开源·音视频开发
Haha_bj2 小时前
Flutter ——device_info_plus详解
android·flutter·ios
一个处女座的程序猿4 小时前
AI之Agent之VibeCoding:《Vibe Coding Kills Open Source》翻译与解读
人工智能·开源·vibecoding·氛围编程
一只大侠的侠5 小时前
React Native开源鸿蒙跨平台训练营 Day16自定义 useForm 高性能验证
flutter·开源·harmonyos
IvorySQL5 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day11从零开发商品详情页面
flutter·开源·harmonyos
一只大侠的侠6 小时前
React Native开源鸿蒙跨平台训练营 Day18自定义useForm表单管理实战实现
flutter·开源·harmonyos
一只大侠的侠6 小时前
React Native开源鸿蒙跨平台训练营 Day20自定义 useValidator 实现高性能表单验证
flutter·开源·harmonyos
山水域7 小时前
SKAdNetwork 6.0 深度实战:多窗口转化值(Conversion Value)建模与数据分层架构
ios