iOS 视图与视图层次结构
jieyan
8年前
<ul> <li> <p><strong>视图基础</strong></p> </li> </ul> <ol> <li> <p>视图是 UIView 对象,或者其子对象。</p> </li> <li> <p>视图知道如何绘制自己。</p> </li> <li> <p>视图可以处理事件,例如触摸(touch)。</p> </li> <li> <p>视图会按照层次结构排列,位于视图层次结构顶端的是应用窗口。</p> </li> </ol> <ul> <li> <p><strong>视图层次结构</strong></p> </li> </ul> <p>任何应用有且只有一个 UIWindow 对象。 UIWindow 对象就像是一个容器,负责包含应用中的所有的视图。应用需要在启动时创建并设置 UIWindow 对象,然后为其添加其他视图。</p> <p>加入窗口的视图会成为该窗口的 <strong>子视图</strong> 。窗口的子视图还可以有自己的子视图,从而构成一个以 UIWindow 对象为根视图的,类似于树形结构的视图层次结构。</p> <p>视图层次结构形成之后,系统会将其绘制到屏幕上,绘制过程可以分为两步:</p> <p>1.层次结构中的每个视图(包括 UIWindow 对象)分别绘制自己。视图会将自己绘制到图层( layer )上,每个 UIView 对象都有一个 layer 属性,指向一个 CALayer 类的对象</p> <p>2. 所有视图的图层何曾一幅图像,绘制到屏幕上。</p> <p>获取当前应用程序的 UIWindow 方法是 UIWindow * keyWindow = [UIApplication sharedApplication].keyWindow;</p> <ul> <li> <p><strong>创建UIView子类 </strong></p> </li> </ul> <p>首先创建一个UIView子类。</p> <p>视图及其frame属性</p> <p>在控制器中创建一个CGRect结构,然后使用该结构创建一个视图对象,并将这个视图对象加入到控制器视图子视图上。</p> <pre> #import "ViewController.h" #import "JXHypnosisView.h" // 为创建的子类 @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建 CGRect 结构 CGRect rect = CGRectMake(100, 100, 100, 200); // 创建视图 JXHypnosisView * firstView = [[JXHypnosisView alloc] initWithFrame:rect]; firstView.backgroundColor = [UIColor redColor]; // 将视图添加到控制器View上 [self.view addSubview:firstView]; } @end </pre> <p>显示结果</p> <p><img src="https://simg.open-open.com/show/f2a2d1f773fc0f331c0c823a5af6d43d.png"></p> <p>CGRect 结构包含该另外两个结构:origin 和size 。其中origin 的类型是CGPoint 结构,该结构包含两个float 类型测成员。size 的类型是CGSize 结构,该结构也包含两个float 类型的成员:width 和height 。</p> <p>所以我们创建的视图对象,在上图中可以看出 JXHypnosisView 对象的左上角位于父视图右侧<strong>100点</strong>、下方<strong>200点</strong>的位置。此外,因为这个frame 结构中的size 是<strong>(100,200)</strong> ,所以我们自定义JXHypnosisView 对象的宽度是<strong>100点</strong>、高度是<strong>200点 。</strong></p> <p>我们这里所说的这些值的单位是<strong>点(points)</strong>,不是<strong>像素(pixels)</strong><strong>。</strong> 如果是像素,那么在不同的<strong>Retina</strong>显示屏上显示的大小是不同的。在<strong>Retina</strong> 显示屏上,一个点是两个像素高度。(所以在跟美工沟通的时候最好让他们根据像素来做图片,并且图片的像素大小是点的两倍,或者三倍)。</p> <p>每个视图对象都有一个 superview 属性。将一个视图作为子视图加入另一个视图时,会自动创建相应的反向关联。</p> <ul> <li> <p><strong>在drawRect:方法中自定义绘图 </strong></p> </li> </ul> <p>前面我们编写了一个简单的自定义的JXHypnosisView对象,并且设置了他的一些基本的属性,如位置,大小,颜色等。在本节中我们将在drawRect:方法中编写绘图代码。</p> <p>视图根据drawRect:方法将自己绘制到图层上。UIView 的子类可以覆盖drawRect:方法完成自定义的绘图任务。例如, UIButton的drawRect:方法默认会在frame 表示的矩形区域中心画出一行浅蓝色的文字。</p> <p>覆盖drawRect:后首先应该获取视图从UIView继承而来的bounds 属性,该属性定义了一个矩形范围,表示视图的绘制区域。</p> <p>视图在绘制自己时,会参考一个坐标系,bounds 表示的矩形位于自己的坐标系,而frame 表示的矩形位于父视图的坐标系,但是两个矩形的大小是相同的。</p> <p>frame 和bounds 表示的矩形用法不同。前者用于确定与视图层次结构中其他视图的相对位置,从而将自己的图层与其他视图的图层正确组合成屏幕上的图像。而后者属性用于确定绘制区域,避免将自己绘制到图层边界之外(其视图是相对于自己而言,设置只有宽高有效)。</p> <ul> <li> <p><strong>绘制圆形</strong></p> </li> </ul> <p>接下来在JXHypnosisView的drawRect方法中添加绘图代码,画出一个尽可能大的圆形,但是不能好过视图的绘制区域。</p> <p>首先,需要根据视图的bounds属性找到绘制预期的中心点:</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据bounds计算中心点 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; } @end </pre> <p>然后再比较视图的宽和高,将较小的值的一般设置为圆形的半径:</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); } @end </pre> <ul> <li> <p><strong>UIBezierPath</strong></p> </li> </ul> <p>UIBezierPath 是用来绘制直线或者曲线的一个类。</p> <p>首先要创建一个 UIBezierPath 对象:</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); UIBezierPath * path = [[UIBezierPath alloc] init]; } @end </pre> <p>接下来我们定义 UIBezierPath 对象需要绘制的路径。</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); UIBezierPath * path = [[UIBezierPath alloc] init]; // 以中心点为圆心,radius的值为半径,定义一个 0 到 M_PI * 2.0 弧度的路径(整圆) [path addArcWithCenter:center radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; } @end </pre> <p>路径已经定义好了,但是之定义路径不会进行实际的绘制。我们还需要向 <strong>UIBezierPath</strong> 对象发送消息,绘制之前定制的路径:</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); UIBezierPath * path = [[UIBezierPath alloc] init]; // 以中心点为圆心,radius的值为半径,定义一个 0 到 M_PI * 2.0 弧度的路径(整圆) [path addArcWithCenter:center radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; // 绘制路径 [path stroke]; } @end </pre> <p>绘制结果:</p> <p><img src="https://simg.open-open.com/show/f6a0e0c8c9ff7b7ffaee3f9620423448.png"></p> <p>现在改变圆形的线条的粗细和颜色。</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); UIBezierPath * path = [[UIBezierPath alloc] init]; // 以中心点为圆心,radius的值为半径,定义一个 0 到 M_PI * 2.0 弧度的路径(整圆) [path addArcWithCenter:center radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; // 设置线条宽度为 10 点 path.lineWidth = 10; // 绘制路径 [path stroke]; } @end </pre> <p>下面来改变绘制图形的轨迹颜色</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); UIBezierPath * path = [[UIBezierPath alloc] init]; // 以中心点为圆心,radius的值为半径,定义一个 0 到 M_PI * 2.0 弧度的路径(整圆) [path addArcWithCenter:center radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; // 设置线条宽度为 10 点 path.lineWidth = 10; // 设置绘制颜色为灰色 [[UIColor lightGrayColor] setStroke]; // 绘制路径 [path stroke]; } @end </pre> <p>运行结果:</p> <p><img src="https://simg.open-open.com/show/13e97655e00be78eeec64e2c55bf4bee.png"></p> <p>这里我们可以尝试视图的 backgroundColor 属性不会受到drawRect中代码的影响,通常应该将重写drawRect方法的视图的背景色设置为透明对应于<strong>clearColor)</strong>,这样可以让视图只显示drawRect 方法中绘制的内容。</p> <pre> #import "ViewController.h" #import "JXHypnosisView.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 创建 CGRect 结构 CGRect rect = CGRectMake(100, 200, 200, 300); // 创建视图 JXHypnosisView * firstView = [[JXHypnosisView alloc] initWithFrame:rect]; firstView.backgroundColor = [UIColor redColor]; NSLog(@"%f",firstView.bounds.origin.x); // 将视图添加到控制器View上 [self.view addSubview:firstView]; } </pre> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 设置 JXHypnosisView 对象的背景颜色为透明 self.backgroundColor = [UIColor clearColor]; } return self; } - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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); UIBezierPath * path = [[UIBezierPath alloc] init]; // 以中心点为圆心,radius的值为半径,定义一个 0 到 M_PI * 2.0 弧度的路径(整圆) [path addArcWithCenter:center radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; // 设置线条宽度为 10 点 path.lineWidth = 10; // 设置绘制颜色为灰色 [[UIColor lightGrayColor] setStroke]; // 绘制路径 [path stroke]; } @end </pre> <ul> <li> <p><strong>绘制同心圆 </strong></p> </li> </ul> <p>在 JXHypnosisView中绘制多个同心圆有两个方法,第一个方法是创建多个<strong>UIBezierPath</strong>对象,每个对象代表一个圆形;第二个方法是使用一个<strong>UIBezierPath</strong>对象绘制多个圆形,为每个圆形定义一个绘制路径。很明显第二种方法更好。</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 设置 JXHypnosisView 对象的背景颜色为透明 self.backgroundColor = [UIColor clearColor]; } return self; } - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据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 = hypotf(bounds.size.width, bounds.size.height) / 2.0; UIBezierPath * path = [[UIBezierPath alloc] init]; // 以中心点为圆心,radius的值为半径,定义一个 0 到 M_PI * 2.0 弧度的路径(整圆) [path addArcWithCenter:center radius:radius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; } // 设置线条宽度为 10 点 path.lineWidth = 10; // 设置绘制颜色为灰色 [[UIColor lightGrayColor] setStroke]; // 绘制路径 [path stroke]; } @end </pre> <p>运行结果:可以看到我们已经画出了一些列的同心圆,但是屏幕右边多出了一条奇怪的线条</p> <p><img src="https://simg.open-open.com/show/018d8d16d58bb0dc73744f533ee739a0.png"></p> <p>这是因为单个<strong>UIBezierPath</strong>对象将多个路径(每个路径可以画出一个圆形)连接起来,形成了一个完成的路径。可以将<strong>UIBezierPath</strong>对象想象成一支在纸上画画的铅笔-但是当我们绘制完成一个圆形之后去绘制另外一个圆形时,铅笔并没有抬起,所以才会出现一条很奇怪的线条。</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 设置 JXHypnosisView 对象的背景颜色为透明 self.backgroundColor = [UIColor clearColor]; } return self; } - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据bounds计算中心点 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; // 是最外层圆形成为视图的外接圆 float maxRadius = hypotf(bounds.size.width, bounds.size.height) / 2.0; UIBezierPath * path = [[UIBezierPath alloc] init]; for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { // 用来设置绘制起始位置 [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)]; [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; } // 设置线条宽度为 10 点 path.lineWidth = 10; // 设置绘制颜色为灰色 [[UIColor lightGrayColor] setStroke]; // 绘制路径 [path stroke]; } @end </pre> <p>运行结果:完美</p> <p><img src="https://simg.open-open.com/show/95180f54dda76428eeac30c381924f1f.png"></p> <ul> <li> <p><strong>绘制图像</strong></p> </li> </ul> <p>创建一个UIImage 对象:UIImage * logoImage = [UIImage imageNamed: @" train " ]; ,然后在drawRect方法中将图像会知道视图上:[logoImage drawInRect:someRect]</p> <pre> #import "JXHypnosisView.h" @implementation JXHypnosisView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // 设置 JXHypnosisView 对象的背景颜色为透明 self.backgroundColor = [UIColor clearColor]; } return self; } - (void)drawRect:(CGRect)rect { CGRect bounds = self.bounds; // 根据bounds计算中心点 CGPoint center; center.x = bounds.origin.x + bounds.size.width / 2.0; center.y = bounds.origin.y + bounds.size.height / 2.0; // 是最外层圆形成为视图的外接圆 float maxRadius = hypotf(bounds.size.width, bounds.size.height) / 2.0; UIBezierPath * path = [[UIBezierPath alloc] init]; for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) { // 用来设置绘制起始位置 [path moveToPoint:CGPointMake(center.x + currentRadius, center.y)]; [path addArcWithCenter:center radius:currentRadius startAngle:0.0 endAngle:M_PI * 2.0 clockwise:YES]; } // 设置线条宽度为 10 点 path.lineWidth = 10; // 设置绘制颜色为灰色 [[UIColor lightGrayColor] setStroke]; // 绘制路径 [path stroke]; // 创建UIImage对象 UIImage * logoImage = [UIImage imageNamed:@"train"]; // 绘制图像 [logoImage drawInRect:bounds]; } @end </pre> <ul> <li> <p><strong>深入学习:Core Graphics </strong></p> </li> </ul> <p>UIImage、UIBezierPath和<strong>NSString</strong>都提供了至少一种用于在drawRect中绘图的方法,这些绘图的方法会在drawRect执行时分别将图像,图形,和文本绘制到视图的图层上。</p> <p>无论是绘制JPEG、PDF还是视图的图层,都是由Core Graphics 框架完成的。Core Graphics是一套提供2D绘图功能的C语言API,使用C结构和C函数模拟了一套面向对象的编程机制,并没有OC对象和方法。Core Graphics 中最重要的“对象”是<strong>图形上下文</strong> ,图形上下文是CGContextRef的“对象”,负责存储绘画状态(例如画笔颜色和线条粗细)和绘制内容所处的内存空间。</p> <p>视图的drawRect方法在执行之前,系统首先为视图的图层创建一个图形上下文,然后为绘画状态设置一些默认参数。drawRect方法开始执行时,随着图形上下文不断执行绘图操作,图层上的内容也会随之改变。drawRect执行完毕后,系统会将图层与其他图层一起组合成完整的图像并显示在屏幕上。</p> <p>参与绘图操作的类都定义了改变绘画状态和执行绘图操作的方法,这些方法其实调用了对应的Core Graphics 函数。例如,向<strong>UIColor</strong>对象发送<strong>setCtroke</strong> 消息时,会调用Core Graphics 中的 <strong>CGContextSetRGBSrokeColor</strong> 函数改变当前上下文中的画笔颜色。</p> <p> </p> <p>来自:http://www.cnblogs.com/wang-com/p/5862931.html</p> <p> </p>