【iOS】CALayer-时钟效果

使用CALayer制作一个钟表。

一、分析

钟表由一个表盘和三个指针组成,三个指针分别是时、分、秒,且指针的一个端点是表盘的中心点,指针围绕着表盘中心点顺时针旋转。

表盘就是一张图片,只需要控制指针旋转就可以了。怎么旋转呢?

1.1. 第一步:绘制指针

指针使用CALayer绘制,把指针position设为表盘的中心点,anchorPoint设为(0.5, 1.0),这时候指针的一端就固定在表盘中心点了。

1
2
3
4
5
6
7
8
9
10
11
@property (nonatomic, strong) CALayer *secondsLayer; // 秒针

CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0.0, 0.0, 1.0, 90.0);
layer.anchorPoint = CGPointMake(0.5, 1.0);
layer.position = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
layer.backgroundColor = [UIColor blackColor].CGColor;
layer.shadowOpacity = 1.0;
layer.shadowOffset = CGSizeMake(2.0, 2.0);
[self.layer addSublayer:layer];
self.secondsLayer = layer;

1.2. 第二步:旋转

由于指针旋转一周是360°,每一秒就是6°。指针围绕坐标系z轴旋转,每一秒旋转6°就可以了。

1
2
3
4
5
6
// 每一格(秒)度数
NSInteger angleUnit = 6;
// 秒针弧度
CGFloat secondsAngle = secnds * angleUnit * M_PI / 180;
// 秒针围绕z轴旋转
self.secondsLayer.transform = CATransform3DMakeRotation(secondsAngle, 0, 0, 1);

1.3. 第三步:获取当前时间秒数

获取秒数可以使用NSDate,也可以使用NSCalendar,建议使用后者。因为NSCalendar可以精确获取到当前时区的年月日时分秒,不需要额外去计算。

1
2
3
4
5
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateCmps = [calendar components:NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:[NSDate date]];
NSInteger hour = dateCmps.hour;
NSInteger minute = dateCmps.minute;
NSInteger secnds = dateCmps.second;

1.4. 第四步:添加定时器

有了秒数和旋转度数,只需要添加一个定时器去刷新就可以了,定时器间隔为1s。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(timeValueChanged:) userInfo:nil repeats:YES];
[timer fire];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

// 定时器运行
- (void)timeValueChanged:(NSDictionary *)userInfo {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateCmps = [calendar components:NSCalendarUnitSecond fromDate:[NSDate date]];
NSInteger secnds = dateCmps.second;

// 每一秒度数
NSInteger anglePerSecnds = 6;
// 秒针弧度
CGFloat secondsAngle = secnds * anglePerSecnds * M_PI / 180;
// 秒针围绕z轴旋转
self.secondsLayer.transform = CATransform3DMakeRotation(secondsAngle, 0, 0, 1);
}

1.5. 第五步:完善其他指针

时针和分针的思路与分针一样,分别按照自己的时间度数旋转。

1
2
3
4
5
6
7
8
9
10
11
12
13
/ 每一分度数
NSInteger anglePerMinute = 6;
// 分针弧度
CGFloat minuteAngle = minute * anglePerMinute * M_PI / 180;
// 时针围绕z轴旋转
self.minuteLayer.transform = CATransform3DMakeRotation(minuteAngle, 0, 0, 1);

// 每一小时度数(每一格6度 * 一小时占用5格)
NSInteger anglePerHour = 30;
// 时针弧度
CGFloat hourAngle = hour * anglePerHour * M_PI / 180;
// 时针围绕z轴旋转
self.hourLayer.transform = CATransform3DMakeRotation(hourAngle, 0, 0, 1);

二、完整代码

DBClockView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// DBClockView.h
@interface DBClockView : UIView

/**
* 表盘
*/
@property (nonatomic, strong) UIImage *clockBackgroundImage;

@end

// DBClockView.m
#import "DBClockView.h"

@interface DBClockView ()

@property (nonatomic, strong) UIImageView *clockBgView; // 表盘背景图

@property (nonatomic, strong) CALayer *centerLayer; // 中心点
@property (nonatomic, strong) CALayer *secondsLayer; // 秒针
@property (nonatomic, strong) CALayer *minuteLayer; // 分针
@property (nonatomic, strong) CALayer *hourLayer; // 时针

@property (nonatomic, strong) NSTimer *timer; // 定时器

@end

@implementation DBClockView

- (void)awakeFromNib {
[super awakeFromNib];
[self initConfig];
}

- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initConfig];
}
return self;
}

- (void)layoutSubviews {
[super layoutSubviews];
self.clockBgView.frame = self.bounds;

// 中心点
CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);;
self.centerLayer.position = center;
self.secondsLayer.position = center;
self.minuteLayer.position = center;
self.hourLayer.position = center;
}

// 初始化子视图
- (void)initConfig {
[self addSubview:self.clockBgView];
[self initHour];
[self initMinute];
[self initSeconds];
[self initCenter];

[self startTimer:1.0];
}

// 添加定时器让表针不停旋转
- (void)startTimer:(NSTimeInterval)timeInterval {
if (self.timer) {
[self.timer invalidate];
_timer = nil;
}
self.timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(timeValueChanged:) userInfo:nil repeats:YES];
[self.timer fire];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

// 表盘中心点
- (void)initCenter {
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0.0, 0.0, 6.0, 6.0);
layer.cornerRadius = layer.bounds.size.width * 0.5;
layer.backgroundColor = [UIColor blackColor].CGColor;
[self.layer addSublayer:layer];
self.centerLayer = layer;
}

// 秒针
- (void)initSeconds {
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0.0, 0.0, 1.0, 90.0);
layer.anchorPoint = CGPointMake(0.5, 1.0);
layer.backgroundColor = [UIColor blackColor].CGColor;
layer.shadowOpacity = 1.0;
layer.shadowOffset = CGSizeMake(2.0, 2.0);
[self.layer addSublayer:layer];
self.secondsLayer = layer;
}

// 分针
- (void)initMinute {
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0.0, 0.0, 2.0, 80.0);
layer.anchorPoint = CGPointMake(0.5, 1.0);
layer.backgroundColor = [UIColor blackColor].CGColor;
layer.shadowOpacity = 0.7;
layer.shadowOffset = CGSizeMake(1.0, 1.0);
[self.layer addSublayer:layer];
self.minuteLayer = layer;
}

// 时针
- (void)initHour {
CALayer *layer = [CALayer layer];
layer.bounds = CGRectMake(0.0, 0.0, 2.0, 60.0);
layer.anchorPoint = CGPointMake(0.5, 1.0);
layer.backgroundColor = [UIColor blackColor].CGColor;
layer.shadowOpacity = 0.4;
[self.layer addSublayer:layer];
self.hourLayer = layer;
}

#pragma mark - Private Method
// 定时器运行
- (void)timeValueChanged:(NSDictionary *)userInfo {
// 获取当前时分秒
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateCmps = [calendar components:NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond fromDate:[NSDate date]];
NSInteger hour = dateCmps.hour;
NSInteger minute = dateCmps.minute;
NSInteger secnds = dateCmps.second;

// 每一秒度数
NSInteger anglePerSecnds = 6;
// 秒针弧度
CGFloat secondsAngle = secnds * anglePerSecnds * M_PI / 180;
// 秒针围绕z轴旋转
self.secondsLayer.transform = CATransform3DMakeRotation(secondsAngle, 0, 0, 1);

// 每一分度数
NSInteger anglePerMinute = 6;
// 分针弧度
CGFloat minuteAngle = minute * anglePerMinute * M_PI / 180;
// 时针围绕z轴旋转
self.minuteLayer.transform = CATransform3DMakeRotation(minuteAngle, 0, 0, 1);

// 每一小时度数(每一格6度 * 一小时占用5格)
NSInteger anglePerHour = 30;
// 时针弧度
CGFloat hourAngle = hour * anglePerHour * M_PI / 180;
// 时针围绕z轴旋转
self.hourLayer.transform = CATransform3DMakeRotation(hourAngle, 0, 0, 1);
}

#pragma mark - Setter and Getter
- (void)setClockBackgroundImage:(UIImage *)clockBackgroundImage {
_clockBackgroundImage = clockBackgroundImage;
self.clockBgView.image = clockBackgroundImage;
[self layoutIfNeeded];
}

- (UIImageView *)clockBgView {
if (!_clockBgView) {
_clockBgView = [[UIImageView alloc] init];
_clockBgView.contentMode = UIViewContentModeCenter;
}
return _clockBgView;
}

@end

ViewController使用

1
2
3
4
5
6
7
8
9
10
- (void)viewDidLoad {
[super viewDidLoad];
DBClockView *clockView = [[DBClockView alloc] init];
clockView.backgroundColor = [UIColor redColor];
clockView.bounds = CGRectMake(0.0, 0.0, 240.0, 240.0);
clockView.center = self.view.center;
clockView.clockBackgroundImage = [UIImage imageNamed:@"clock"];
clockView.allowSlowMode = YES;
[self.view addSubview:clockView];
}