之前的文章介绍了数据的传递和存储,这篇文章就介绍显示数据的控件。iPhone之所以这么受欢迎,其中有一个原因是因为它的UI界面非常美观。iOS提供了UIKit框架,里面有各种各样的UI控件。
这里有利用Quartz2D生成的图片(包括裁剪圆形图片,截图,水印图片):https://github.com/shihuaixing/ClipCircleImageDemo.git
UILabel:显示文字
UIImageVIew:显示图片
UIButton:同时显示图片和文字,还能点击
等等。
这些控件进行拼拼凑凑是能够搭建和实现一些简单、常见的UI界面,并显示数据的。
但是有些UI界面极其复杂、而且比较有个性,用普通的UI控件是无法实现的,这个时候可以利用Quartz2D技术奖控件内部的结构画出来,就向自定义控件的样子。其实,iOS中大部分控件的内容都是通过Quarz2D画出来的。
因此,Quarz2D在iOS开发中很重要的一个价值就是:自定义view(自定义控件)。
我们来简单了解一下关于Quarz2D的知识。
Quarz2D:是一个二维绘图引擎,同时支持iOS和Mac系统。
Quarz2D的API是纯C语言的。
Quarz2D的API来自于Core Graphics框架
Quarz2D函数类型和函数基本都以CG为前缀:
CGContextRef
CGPathRef
CGContextStrokePath(ctx)
Quarz2D能完成的简单的工作:
1) 绘制图形:线条/三角形/矩形/圆/弧等
2) 绘制文字
3) 绘制/生成图片
4)读取/生成PDF
5)图/裁剪图片
6)定义控件
7)等等
下面来看一下利用Quarz2D实现的一些效果:
1、图片裁剪:
下面我们在使用Quarz2D之前,简单认识一些概念:
1)图形上下文(Graphics Context):是一个CGContextRef类型的数据
a. 作用:
保存绘图信息,绘图状态;
决定绘制的输出目标(绘制到什么地方)
(输出目标客源是PDF文件、Bitmap或者显示器的窗口上)
b. Quarz2D提供了以下几种类型的Graphics Context:
· Bitmap Graphics Context
· PDF Graphics Context
· Window Graphics Context
· Layer Graphics Context
· Printer Graphics Context
注意:图形上下文(Graphics Context)是一个很重要的类型!
了解了图形上下文(GraphicsContext),那我们如何利用Quarz2D自定义view(自定义控件)?
如何利用Quarz2D绘制内容到view上:
1)首先,得有图形上下文,因为它能保存绘图信息,并且决定这绘制到什么地方去
2)其次,那个图形上下文必须跟view想关联,才能讲内容绘制到view上
自定义view的步骤:
1)新建一个类,继承自UIView
2)实现-(void)drawRect:方法,然后在这个方法中
a. 取得跟当前view相关联的图形上下文
b. 绘制相应的图形内容
c. 利用图形上下文将绘制的所有内容渲染显示到view上那大家可能会要问,为什么要实现drawRect:方法才能将绘图显示到view上呢?最重要的一点:因为在drawRect:方法中才能取到跟view相关联的图形上下文。
drawRect: 方法在什么时候被执行呢?了解:
1)view内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer GraphicsContext,因此,绘制的内容其实是绘制到view的layer上去了。
2)view之所以能显示东西,完全是因为它内部的layer(图层)
下面通过代码显示Quarz2D的功能:
创建新项目,在控制器view上拖一个自定义view(redView),创建自定义view类(HXView)。将自定义的view的类设置为HXView。
1、画线、画圆、画弧
- (void)drawBasisGraphics {
/***************画线*****************/
// 获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 画线
// 1.起点
CGContextMoveToPoint(ctx, 10, 10);
// 添加线段
CGContextAddLineToPoint(ctx, 100, 100);
CGContextAddLineToPoint(ctx, 200, 30);
// 设置线宽
CGContextSetLineWidth(ctx, 10);
// 设置线颜色
CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
// 设置两头部样式
CGContextSetLineCap(ctx, kCGLineCapRound);
// 设置连接处样式
CGContextSetLineJoin(ctx, kCGLineJoinRound);
// 渲染并显示到view上
CGContextStrokePath(ctx);
/***************画圆*****************/
// 1.通过椭圆画出圆
// 1)(不填充)
// CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
//
// // 设置线宽
// CGContextSetLineWidth(ctx, 10);
// // 设置线颜色
// CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
//
// // 渲染并显示到view上
// CGContextStrokePath(ctx);
// 2)(填充)
// CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
//
// // 设置线颜色
// CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
//
// // 渲染并显示到view上
// CGContextFillPath(ctx);
// 2.根据圆点、半径(可以画圆弧)
// 1)不填充
CGContextAddArc(ctx, 100, 100, 50, 0, M_PI_2, 0);
// CGContextAddArc: 参数说明
/*
CGContextRef c:图形上下文
CGFloat x, CGFloat y:圆点坐标
CGFloat radius:圆的半径
CGFloat startAngle:画圆的开始角度(逆时针为负,顺时针为正)
CGFloat endAngle:画圆的结束角度
int clockwise:画图方向,0:顺时针,1:逆时针
*/
// 设置线颜色
CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
// 渲染并显示到view上
CGContextStrokePath(ctx);
// 2)填充
// CGContextAddArc(ctx, 100, 100, 50, 0, M_PI_2, 0);
// // 设置线颜色
// CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
//
// // 渲染并显示到view上
// CGContextFillPath(ctx);
/***************画弧*****************/
// 当前点
CGContextMoveToPoint(ctx, 50, 100);
CGContextAddCurveToPoint(ctx, 0, 0, 100, 100, 50, 0);
// set : 同时设置为实心和空心颜色
// setStroke : 设置空心颜色
// setFill : 设置实心颜色
[[UIColor whiteColor] set];
CGContextStrokePath(ctx);
}
2、画图,并裁剪
// 画图
- (void)drawImage:(CGRect)rect {
// 获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIImage *image = [UIImage imageNamed:@"008.jpg"];
// [image drawAsPatternInRect:rect];// 平铺
// [image drawInRect:rect]; // 拉伸
[image drawAtPoint:CGPointMake(0, 0)];// 从父控件的0,0点开始画图
// 渲染显示到view上
CGContextStrokePath(ctx);
}
// 裁剪圆形图片
- (void)drawCircleImage:(CGRect)rect {
// 获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIImage *image = [UIImage imageNamed:@"008.jpg"];
// 1.画圆
CGContextAddEllipseInRect(ctx, CGRectMake((rect.size.width - image.size.width) * 0.5, (rect.size.height - image.size.height) * 0.5, image.size.height, image.size.width));
// 超出该圆的内容全部裁剪掉
CGContextClip(ctx);// 裁剪当前图形上下文
CGContextClipToRect(ctx, CGRectMake((rect.size.width - image.size.width) * 0.5, (rect.size.height - image.size.height) * 0.5, image.size.height, image.size.width)); // 裁剪范围
// 画图
[image drawAtPoint:CGPointMake((rect.size.width - image.size.width) * 0.5, (rect.size.height - image.size.height) * 0.5)];
CGContextSetLineWidth(ctx, 10);
// 渲染显示到view上
CGContextStrokePath(ctx);
}
3、画文字(使用Quartz2D画出来的文字是反着的,跟坐标系有关)
- (void)drawText:(CGRect)rect {
NSString *textString = @"哈哈哈哈shx";
NSMutableDictionary *attris = [NSMutableDictionary dictionary];
attris[NSFontAttributeName] = [UIFont systemFontOfSize:20];
attris[NSForegroundColorAttributeName] = [UIColor whiteColor];
[textString drawInRect:rect withAttributes:attris];
}
下面我们来做一个画板:
清空:清除画板上的所有内容;
回退:清除在画板上画出的最后一条线
创建新项目,在控制器view上拖一个自定义view(redView),创建自定义view类(HXView)。将自定义的view的类设置为HXView。
因为手指要在view上触摸才能有事件发生,所以要在HXView.m文件中重写如下方法:
/**
* 手指开始触摸view的时候调用
* 在该方法中可以确定线的起点
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
/**
* 手指在view上移动时调用
* 在该方法中连接其他点
*/
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
/**
* 手指触摸结束时候调用
* 在该方法中处理一条路径画完之后的事情
*/
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
先来了解一下思路:
每当手指开始触摸view的时候就会调用touchesBegan方法,每次调用该方法时就创建一个新的路径(path:n个点组成),把touchesBegan获得的点(起点)添加到这条新的路径中(数组存储点);当手指在view上移动的时候就会调用touchesMoved方法,每次调用该方法就会返回手指所在的位置,把该位置添加到path中;当手指在view上触摸结束的时候就会调用touchesEnded方法,在该方法中项path中添加最后的一个点。至此,path路径上的所有点都被存储在path中了。现在可以利用view的drawRect:方法开始绘制路径了。
由于画板可能会绘制很对条路径,所以我们也要把所有的路径存起来。(数组存储路径)
注意:每次向path中添加点的时候都要主动调用setNeedsDisplay方法,该方法会调用drawRect:方法随时绘制路径。
现在来实现这些方法
在touchesBegan方法中,能获得手指开始触摸时候的位置(一个路径的起点)
/**
* 手指开始触摸view的时候调用
* 在该方法中可以确定线的起点
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 获得开始触摸的点(起点)
CGPoint startPoint = [self pointWithTouch:touches];
// 存储新路径上的所有的点
NSMutableArray *currentPath = [NSMutableArray array];
// 新路径存储新路径的第一个点(新路径起点)
[currentPath addObject:[NSValue valueWithCGPoint:startPoint]];
// 将新路径存储到数组中
[self.paths addObject:currentPath];
// 重新绘制
[self setNeedsDisplay];
}
获得点的方法
- (CGPoint)pointWithTouch:(NSSet * )touches {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:touch.view];
return point;
}
手指开始移动:
/**
* 手指在view上移动时调用
* 在该方法中连接其他点
*/
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 获取其他点
CGPoint otherPoint = [self pointWithTouch:touches];
// 取出最后一个路径(当前正在画的路径)
NSMutableArray *currentPath = [self.paths lastObject];
// 存储其他点
[currentPath addObject:[NSValue valueWithCGPoint:otherPoint]];
// 重新绘制
[self setNeedsDisplay];
}
手指触摸事件结束:(考虑为什么只要调用touchesMoved方法)
/**
* 手指触摸结束时候调用
* 在该方法中处理一条路径画完之后的事情
*/
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 手指离开后,也是连接点
[self touchesMoved:touches withEvent:event];
}
回退功能(移除存储路径的数组的最后一个元素):
- (void)revoke {
[self.paths removeLastObject];
[self setNeedsDisplay];
}
清空功能(移除存储路径的数组的所有元素);
- (void)clear {
[self.paths removeAllObjects];
[self setNeedsDisplay];
}
注意:每次改变路径内容的时候都要调用setNeedsDisplay方法!
效果:
回退:
清空:
只要自定义一个view,实现它的drawRect:方法,就可以在这个view上画出你想要的内容。大家可以试着实现以下前途的手势解锁功能的界面。(以上部分图片引用自mj老师)