iOS极速入门教程(四)一个demo演示如何自定义UIView

简介

上文演示了如何通过xib写一些小东西,本文通过写一个绘制同心圆的UIView的子类来演示如何通过源码的方式写自定义View,绘制若干个同心圆,类似Android里重写View的onDraw方法绘制特殊图案。同时添加触摸改变这些圆圈的颜色,文章内容来自《iOS编程第四版》的第5章。

视图基础

  1. 视图是UIView或子类对象;
  2. 视图知道如何绘制自己;
  3. 视图可以处理事件,如touch;
  4. 任何一个应用有且只有一个UIWindow对象,它是一个容器,负责包含应用中所有试图。应用需要在启动时创建并设置UIWindow对象,然后为其添加其他视图。
  5. 每个视图(包括UIWindow)分别绘制自己到图层(layer)上,每个UIView对象都有一个layer属性,指向一个CALayer对象

本文效果


点击圆圈会变色。

创建UIView子类

创建一个CircleView的子类,继承自UIView,CircleView.m源码如下:

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
//
// CircleView.m
// HelloWorld
//
// Created by yanzi on 2018/10/3.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import "CircleView.h"
@interface CircleView()
@property (nonatomic, strong) UIColor *color;
@end
@implementation CircleView
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
self.backgroundColor = [UIColor whiteColor];
self.color = [UIColor greenColor];
// self.userInteractionEnabled = YES;
// self.multipleTouchEnabled = YES;
}
return self;
}
- (void)drawRect:(CGRect)rect{
CGRect bounds = self.bounds;
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
// float radius = (MIN(bounds.size.width, bounds.size.height)) / 2.0;
float maxRadius = (hypot(bounds.size.width, bounds.size.height)) / 2.0;
UIBezierPath *path = [[UIBezierPath alloc] init];
for(float r = maxRadius; r >0; r-= 20){
[path moveToPoint:CGPointMake(center.x + r, center.y)];
[path addArcWithCenter:center radius:r startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
}
path.lineWidth = 8;
// [[UIColor lightGrayColor] setStroke];
[_color setStroke];
[path stroke];
//将图片画在中心处
UIImage *image = [UIImage imageNamed:@"cat.png"];
float w = image.size.width;
float h = image.size.height;
CGRect rect2 = CGRectMake(center.x - w / 2, center.y - h / 2, w, h);
[image drawInRect:rect2];
//绘制一行文字
NSString *text = @"天蚕变的练习";
[text drawAtPoint:CGPointMake(0, 20) withAttributes:nil];
}
//重写函数监听touch事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touchesBegan enter...");
float red = (arc4random() % 100) / 100.0;
float green = (arc4random() % 100) / 100.0;
float blue = (arc4random() % 100) / 100.0;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
self.color = color;
[self setNeedsDisplay];
}
@end

AppDelegate.m里使用这个自定义View:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(@"didFinishLaunchingWithOptions");
// Override point for customization after application launch.
self.window = [[UIWindow alloc]initWithFrame:
[[UIScreen mainScreen] bounds] ];
CGRect frame = self.window.bounds;
CircleView *view = [[CircleView alloc]initWithFrame: frame];
// [self.window addSubview:view];
self.window.rootViewController = [[UIViewController alloc]init];
self.window.backgroundColor = [UIColor whiteColor];
[self.window.rootViewController.view addSubview:view];
[self.window makeKeyAndVisible];
return YES;
}

要点解读

  1. 在AppDelegate使用这个View时, 需要先给self.window设置一个空的UIViewController否则会报异常;
  2. 后面通过touchesBegan捕捉touch事件,但是根据书上代码实践发现不起作用。原因是设的空的UIViewController把这个自定义View给挡住了,通过以下方法解决:
1
2
3
4
5
// [self.window addSubview:view];
//下面两句代替上面一句解决touchBegan不起作用问题
self.window.rootViewController = [[UIViewController alloc]init];
[self.window.rootViewController.view addSubview:view];

当使用前者的视图层级:

即window里有个CircleView,然后下面又有个UIViewController,这个Controller又自带一个UIView,这导致CircleView无法在最前台接收到触摸事件。
采用后者的视图层级:

  1. 绘制图片:
1
2
3
4
5
6
//将图片画在中心处
UIImage *image = [UIImage imageNamed:@"cat.png"];
float w = image.size.width;
float h = image.size.height;
CGRect rect2 = CGRectMake(center.x - w / 2, center.y - h / 2, w, h);
[image drawInRect:rect2];

其中CGRect的前两个参数是坐上顶点坐标,后两个参数是宽和高。Rect有多大,这个UIImage就自动缩放到多大。

  1. iOS重绘机制
    当属性发生变化时,必须向视图发送setNeedsDisplay,类似Android里的invidate()触发一次重汇。

参考

显示 Gitment 评论