iOS之quartz2D绘制钟表走时

写在前面

Quartz2D是苹果提供的一套高效绘图的引擎,不是框架。。里面可以自定义许多的控件,一般的控件都是继承自UI~,而使用Quartz2D可以做出我们自己想要的类型的控件出来。这里就试着用Quartz2D来画一个时钟走表的控件。

关于Quartz2D的绘制的步骤

绘制Quartz2D一般分为以下几个步骤:

  • 1.上下文的获取 (获取或者创建都是以UIGraphics开头)
    上下文已经是创建好了的,只要获取就可以了。而且只有当系统调用-drawRect:这个方法的时候,系统才会去创建上下文,如果就单纯的手动调用-drawRect:这个方法,是不会去创建上下文,这一点很重要。
  • 2.绘制路径
    这个路径也就是相当于我们用笔在一张白纸上画的内容,包括了内容的颜色,是直线还是曲线等状态。
  • 3.把绘制的内容保存到上下文中
    我们用笔在纸张上画图,直接就显示出来我们所画的内容了,而计算机没那么智能,所以我们要手动的保存到上下文中。
  • 4.把上下文内容显示渲染到view的layer上去(stroke/fill)
    这一步就是把画的内容显示到需要展示的view中,这里展示的是view的layer层上。其实还可以展示到PDF中,printer(打印机),bitmap(一个绘图工具)等。

0.界面相关

既然是自定义的view,那么久要创建一个view继承自UIView,然后再view中去重写- drawRect: 这个方法。然后回到控制器去创建view,并且展示出来。

1.绘制表盘

1.1绘制圆,

    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    CGFloat radius = rect.size.width * 0.5 - 10;
    // 圆
    UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2)];
    [circle setLineWidth:3.0f];
    [[UIColor lightGrayColor] set];
    CGContextAddPath(ctx, circle.CGPath);
    CGContextStrokePath(ctx);

这就把圆绘制好了。

1.2绘制刻度

表盘上的刻度是每6°绘制一次的。我们这里需要把刻度那根直线的一个顶点定位到圆环上,然后另一个顶点根据是不是5的倍数来定。
首先就要确定刻度线在圆上的顶点:
都知道圆的标准公式为:

(x-a)^2+ (y-b)^2 = r^2; 

我们可以得知x与y的值:

// 这是按照笛卡尔的坐标系的计算的结果,并不是以iOS的坐标系的结果。
// angle代表的是角度
x = radius * cos(angle) ; 
y = radius * sin(angle);

转换成iOS的坐标系:

CGFloat x = center.x + radius * cos(angle) ;
CGFloat y = center.y + radius * sin(angle);

这个是在圆上的点,那么同理我们可以计算另一个点,只是修改下半径就可以得到。

// 一般的短刻度线
CGFloat x0 = center.x + (radius - 10) * cos(angle); 
CGFloat y0 = center.y + (radius - 10) * sin(angle);
// 整点时的长刻度线        
CGFloat x1 = center.x + (radius - 20) * cos(angle);
CGFloat y1 = center.y + (radius - 20) * sin(angle);

这里的angle传入的是以-90°开始的角度,也就是12点的位置开始的角度。
CGFloat angle = -M_PI_2 + i / 60.0f * M_PI * 2;
依次循环添加到表盘上。
最后展示的效果如下:

iOS之quartz2D绘制钟表走时_第1张图片
表盘样式

2.绘制指针

秒针的绘制基本思路还是画一条直线,然后根据时间的来进行修改。

2.1定时器

这里就需要用到定时器了,需要重复执行绘制图形的操作,所以首先在控制器中添加定时器。

    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
        [self catchTime];
    }];
    [self.timer fire];

一定要注意的是在页面销毁的时候,也需要把定时器进行销毁。

2.2秒针、分针、时针

指针的起点在圆心出,终点可以在圆上或不在圆上,我在这里设置的不在圆上,因为有刻度线,所以留有一定的距离。

// 起点
CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
[path moveToPoint:center];

需要注意的是这里的point需要指明的是这个view所在的中心点,如果直接写self.view.center,这样会找到这个自定义view的父控件上去,到时候绘制出来的效果可能有偏差。

    // 终点
    // 每一次的角度。-M_PI_2 起始角度,也就是12点的位置。
    CGFloat angle = -M_PI_2 + self.seconds / 60.0 * M_PI * 2; // 外部传入的时间。。秒
    // 圆上的点
    CGFloat radius = rect.size.width * 0.5 - 15; // 秒针的长度
    CGFloat x = center.x + radius * cos(angle) ;
    CGFloat y = center.y + radius * sin(angle);
    [path addLineToPoint:CGPointMake(x, y)];

分针和时针的做法同上。
这样就把三条指针所在的线绘制出来了。


iOS之quartz2D绘制钟表走时_第2张图片
指针

3.走时

3.1定时器的选择

关于定时器,可以采用NSTimer方式或者是CADisplayLink,然后加入到NSRunloop中去。

    // 方式一:
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {
//        [self catchTime];
        [self catchTimeDict];
    }];
    [self.timer fire];
// 方式二:
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(catchTime)];
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

方式二的刷新频率(1/60)过快,所以演示起来有点辣眼睛。

3.2刷新并走时

最后让这个指针动起来,所以就应该在控制器中去调用这个自定义的view。指示当前的时间,需要外部传入,也就是说自定义的view需要定义三个属性,让外部传入时分秒。

/* sencond*/
@property (nonatomic, assign) int seconds;

/* minute*/
@property (nonatomic, assign) int minute;

/* hour*/
@property (nonatomic, assign) int hour;

计算时分秒,进行调用:

    // 方法一:
    NSString *timeStamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
    self.wjClockView.seconds = [WJTime wjTimeSecondsWithTimeStamp:timeStamp];
    self.wjClockView.minute = [WJTime wjTimeMinuteWithTimeStamp:timeStamp];
    self.wjClockView.hour = [WJTime wjTimeHourWithTimeStamp:timeStamp] - 12; // 这里是标注的是12小时制,所以要减去12.
    self.wjTimeLable.text = [NSString stringWithFormat:@"%02d:%02d:%02d", self.wjClockView.hour + 12, self.wjClockView.minute, self.wjClockView.seconds]; // 这里是在标签中显示24小时制,所以加12
    // 方法二
    NSString *timeStamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
    NSDictionary *timeDict = [WJTime wjTimeGetTimeDictWithTimeStamp:timeStamp];
    NSNumber *seconds = timeDict[@"seconds"];
    NSNumber *minute = timeDict[@"minute"];
    NSNumber *hour = timeDict[@"hour"];
    self.wjClockView.seconds = seconds.intValue;
    self.wjClockView.minute = minute.intValue;
    self.wjClockView.hour = hour.intValue - 12;
    self.wjTimeLable.text = [NSString stringWithFormat:@"%02d:%02d:%02d", self.wjClockView.hour + 12, self.wjClockView.minute, self.wjClockView.seconds];

- wjTimeSecondsWithTimeStamp : 这些方法是我自己写的一些类,专门返回现在时刻的时分秒的,只要传入当前的时间戳就可以了。
这样就可以显示当前时间的效果,还有一点问题就是,这个虽然调用了这个方法,但是不走时。
解决这个问题,我们可以在自定义view中进行重写seconds属性的setter方法,然后看看手动调用-drawRect:方法,看这个方法能否实现。

// 重写setter方法
- (void)setSeconds:(int)seconds {
    _seconds = seconds;
    [self drawRect:self.frame]; // 不可行方式
}

但是很遗憾,并不能实现。
因为在开篇的时候就讲了,调用-drawRect :方法时,如果是系统调用的话,那么会自动创建一个上下文,只要获取就可以了,但是如果是手动调用的话,就不会创建这个上下文,也就是说没法进行重绘。所以这种手动调用的方法失效。
其实调用系统提供的另一个方法可以实现:- setNeedsDisplay,这个方法,系统会自动的去调用-drawRect :这个方法,也就是说系统也会自动的去创建上下文,所以可以进行重绘制图形,而-setNeedsDisplay在调用-drawRect :方法的时候,也不是马上去调用修改的,而是在建立一个flag标识,当屏幕刷新的时候进行调用-drawRect :这个方法,而屏幕刷新是在什么时候去刷新,屏幕刷新会在1s中刷新60次。

//  重写setter方法
- (void)setSeconds:(int)seconds {
    _seconds = seconds;
    [self setNeedsDisplay]; // 可行方式
}

以上就可以发现走时功能实现了。
但是以上的走时,总感觉是一顿一顿的。分针和时针的走时不是那么顺滑,所以这里也需要对minute和hour属性的setter重写。
而且对于分针和时针转动的角度,也是需要修改,为的是在走时的时候,分针和时针比较顺滑的走动,而不是走到秒针走了一圈,分针才开始跳一格。

// 分针的角度
CGFloat angle = -M_PI_2 + (self.minute / 60.0) * M_PI * 2 + self.seconds / 3600.f * M_PI * 2;
// 时针的角度
CGFloat angle = -M_PI_2 + (self.hour / 12.0) * M_PI * 2 + self.minute / 720.f * M_PI * 2 + self.seconds / (12 * 3600.f) * M_PI * 2;

到这一步就是基本上完成了钟表走时的功能了。
添加logo .

demo传送门

你可能感兴趣的:(iOS之quartz2D绘制钟表走时)