在iOS上画图主要有3种方法:
- UIKit, 这是我们最常用的绘图方法,平时的UIButton、UIImageView就是使用UIkit实现的。
- Core Graphics, 这是是苹果提供一套基于C的API框架,使用了Quartz2D作为绘图引擎。Quartz2D是既可以在iOS上使用,也可以在Mac OS上使用的垮平台2D绘图引擎。
- OpenGL ES, 是种编程规范,也就是说它只定义了一套规范,具体的实现由设备制造商根据规范去做。
在上节UIBezierPath画一个拱形的tabBar图形章节中,我们使用它画了一个带拱形的View。可以看到UIBezierPath其实就是对Core Graphics的封装,直接调用API就可以简单的画直线、圆、曲线,省去了对Path的操作细节。
一. Core Graphics使用简介
Core Graphics是基于C语言的API,所以它不是面向对象的,在使用时需要一个图形上下(CGContext)。Context其实就是暂时缓存图形,等绘制完毕后将图形返还给所需的对象。
1.1 如何在UIView上使用Graphics绘图?
使用Core Graphics需要一个画布(CGContext),而UIView只能在-(void)drawRect:(CGRext)rect方法中获取当前UIView的相关联的图形上下文,所以Core Graphics只能在-(void)drawRect:(CGRext)rect方法中使用。
1.2 Core Graphics绘图七大步骤
- 获取上下文
- 设置画图起点
- 设置绘图类别:直线、圆、圆弧、矩形
- 设置绘图线条颜色和线条宽度
- 开始绘图
- 结束绘图
1.3 绘制实例
- 绘制直线:
-(void)drawRect:(CGRect)rect
{
// 1. 获取图像上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2. 设置起点
CGContextMoveToPoint(ctx, 10, 100);
// 3. 画直线
CGContextAddLineToPoint(ctx, 100, 100);
// 4. 设置线条颜色 红色
CGContextSetRGBStrokeColor(ctx, 1.0, 0, 0, 1.0);
// 5. 设置线条宽度
CGContextSetLineWidth(ctx, 10);
// 6. 设置线条的起点和终点的样式
CGContextSetLineCap(ctx, kCGLineCapRound);
// 7. 设置线条的转角的样式
CGContextSetLineJoin(ctx, kCGLineJoinRound);
// 8. 开始绘制
CGContextStrokePath(ctx);
}
- 绘制矩形
-(void)drawRect:(CGRect)rect
{
// 绘制四边形
// 1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.绘制四边形
CGContextAddRect(ctx, CGRectMake(10, 10, 150, 100));
[[UIColor colorWithRed:1.0 green:0 blue:0 alpha:1.0] set];
// 3.渲染图形到layer上
// 填充颜色
CGContextFillPath(ctx);
// 路径颜色
// CGContextStrokePath(ctx);
}
- 绘制圆形
-(void)drawRect:(CGRect)rect
{
// 画圆弧
// 1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.画圆弧
// x/y 圆心
// radius 半径
// startAngle 开始的弧度
// endAngle 结束的弧度
// clockwise 画圆弧的方向 (0 顺时针, 1 逆时针)
CGContextAddArc(ctx, 100, 100, 50, 0, M_PI * 2, 1);
// CGContextClosePath(ctx);
// 3.设置线条颜色
[[UIColor redColor] set];
// 4. 绘图
CGContextStrokePath(ctx);
// CGContextFillPath(ctx);
}
思考:
- CGContextStrokePath(ctx); 和 CGContextFillPath(ctx);有啥区别?
- 如何设置圆形的线条颜色以及实心圆的背景色?
1.4 如何在drawRect方法外绘图
使用UIGraphicsGetCurrentContext()绘图有个限制条件就是必须在-(void)drawRect:(CGRect)rect方法中使用,实际应用中显然不够灵活。那么如何在此方法以外的地方绘制图形了?常用的有两种方法:
a. UIGraphicsBeginImageContext(size)绘制位图
b. 贝塞尔曲线+CAShapeLayer绘图
- 使用UIGraphicsBeginImageContext(CGSize size)绘图
-(void)viewDidLoad
{
[super viewDidLoad];
UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
imgView.image = [UIImage imageNamed:@"liuYan.jpeg"];
imgView.backgroundColor = [UIColor blueColor];
UIImage *image = [UIImage imageNamed:@"liuYan.jpeg"];
NSLog(@"图片大小%f %f", image.size.width,image.size.height );
// 我们重新绘图,将图片在100x100大小的画布上绘制
CGSize size = CGSizeMake(100, 100);
// 设置画图大小
UIGraphicsBeginImageContext(CGSizeMake(size.width, size.height));
[image drawInRect:CGRectMake(0,0,100, 100)];
UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
NSLog(@"修改后大小%f %f", scaledImage.size.width,scaledImage.size.height );
imgView.image = scaledImage;
UIGraphicsEndImageContext();
}
从代码可以看到我们使用UIGraphicsBeginImageContext(CGSize size)这个方法也可以绘制位图,而不需要在drawRect方法中绘制。
打印图片绘制前后大小:
从截图中可以看到我们将500x600大小的图片重新绘制后,产生的新图片scaledImage大小为100x100了。
UIGraphicsBeginImageContext(CGSize size)方法中的CGSize size参数即图形上下文中的画布大小,通过改变这个参数值,绘制出随意大小的图片。
-(void)drawInRect:(CGRect)rect; 方法中的rect参数告诉画布我需要在哪个点上画多大的图像。例如将 [image drawInRect:CGRectMake(0,0,100, 100)];改为 [image drawInRect:CGRectMake(25,25,50, 50)];
图像跑到了画布的中间位置,并且图像大小变成了50x50,但scaledImage仍然是100x100,说明画布的其他位置并没有东西填充。
P.S 使用此方法可以将一张图片进行缩放。
- 贝塞尔曲线+CAShapeLayer绘图
-(void)viewDidLoad {
[super viewDidLoad];
UIView *rectView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
rectView.backgroundColor = [UIColor redColor];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rectView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(20,5)];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.frame = rectView.bounds;
layer.path = path.CGPath;
rectView.layer.mask = layer;
[self.view addSubview:rectView];
}
Core Graphic部分代码上传 gitHub。
1.5 Core Graphics画文字和图片
主要是 darwAtPoint和drawInRect 这两个方法的使用
// 画文字
-(void)drawRect:(CGRect)rect {
// Drawing code
NSString *str = @"Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统";
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
//设置文字大小
dict[NSFontAttributeName] = [UIFont systemFontOfSize:12];
//设置文字颜色
dict[NSForegroundColorAttributeName] = [UIColor greenColor];
//设置描边宽度
dict[NSStrokeWidthAttributeName] = @2;
//设置描边颜色
dict[NSStrokeColorAttributeName] = [UIColor blueColor];
//设置阴影
NSShadow *shadow = [[NSShadow alloc] init];
//设置阴影的便宜量
shadow.shadowOffset = CGSizeMake(1, 1);
//设置阴影颜色
shadow.shadowColor = [UIColor greenColor];
//设置阴影模糊程序
shadow.shadowBlurRadius = 1;
dict[NSShadowAttributeName] = shadow;
/**
AtPoint:文字所画的位置
withAttributes:描述文字的属性.
*/
//不会自动换行
[str drawAtPoint:CGPointMake(0,0) withAttributes:dict];
//会自动换行.
[str drawInRect:self.bounds withAttributes:dict];
}
// 画图片
-(void)drawRect:(CGRect)rect {
// Drawing code
//1.加载图片
UIImage *image = [UIImage imageNamed:@"liuYan.jpeg"];
//绘制出来的图片,是保持原来图片大小
[image drawAtPoint:CGPointZero];
//把图片填充到这个rect当中.
[image drawInRect:rect];
//添加裁剪区域 .把超区裁剪区域以外都裁剪掉
// UIRectClip(CGRectMake(0, 0, 50, 50));
//平铺
// [image drawAsPatternInRect:self.bounds];
// //快速的画出一个矩形
// [[UIColor blueColor] set];
// UIRectFill(CGRectMake(10, 10, 100, 100));
}
二. UIBezierPath使用简介
UIBezierPath其实是对Core Graphics Path的封装, 需要与CAShapeLayer配合使用。
2.1 UIBezierPath使用六部曲
- 创建贝塞尔曲线
+(instancetype)bezierPath;
- 添加子路径
// 从point开始绘制路径
-(void)moveToPoint:(CGPoint)point;
- 设置绘图类别:直线、圆弧、矩形
// 添加一条直线路径
-(void)addLineToPoint:(CGPoint)point;
// 添加一个圆形路径
-(void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle
// 添加一段圆弧路径
-(void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
- 设置路径颜色
[[UIColor redColor] set];
- 开始画路径
[path stroke];
- 结束路径绘制
-(void)closePath;
主要就以上6个步骤就可以快速的画出图形。
注意:如果需要画一个实心View,可以使用 [[UIColor blackColor] setFill];设置填充颜色。
2.2 绘制实例
- 直线
-(void)drawRect:(CGRect)rect
{
//1.获取图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//上下文的状态(线的粗细、颜色、链接样式等)
//设置线的粗细
CGContextSetLineWidth(ctx, 10);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetLineCap(ctx, kCGLineCapRound);
//2. 绘制路径
UIBezierPath *path = [UIBezierPath bezierPath];
//2.1 设置起点
[path moveToPoint:CGPointMake(50, 250)];
//2.2 添加一根线到终点
[path addLineToPoint:CGPointMake(250, 50)];
//画第二条直线(新起点)
[path moveToPoint:CGPointMake(100, 250)];
[path addLineToPoint:CGPointMake(250, 100)];
//把上一条线的终点当做起点来画第二条线
[path addLineToPoint:CGPointMake(250, 250)];
[[UIColor redColor] setStroke];
//3.把绘制的内容添加到上下文中
// UIBezierPath:UIKit框架 ---> CGPathRef:CoreGraphics框架
CGContextAddPath(ctx, path.CGPath);
//4.把上下文的内容显示到view上(渲染到view的layer上)渲染的方式有两种Stroke(描边)、Fill(填充)
CGContextStrokePath(ctx);
}
- 矩形
-(void)drawRect:(CGRect)rect
{
//1.获取图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
//设置颜色
[[UIColor redColor]set];
//2.绘制路径
//画矩形
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 100, 100)];
//画圆角矩形
// UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) cornerRadius:50];
//3.把路径添加到上下文
CGContextAddPath(ctx, path.CGPath);
//4.把上下文渲染到view上
//描边
// CGContextStrokePath(ctx);
//填充
CGContextFillPath(ctx);
}
贝塞尔曲线部分代码上传到gitHub。
Quartz2D内存管理
Quartz2D是C语言的框架,部分地方需要自己管理内存:
•使用含有“Create”或“Copy”的函数创建的对象,使用完后必须释放,否则将导致内存泄露
•使用不含有“Create”或“Copy”的函数获取的对象,则不需要释放
•如果retain了一个对象,不再使用时,需要将其release掉。
可以使用Quartz 2D的函数来指定retain和release一个对象。例如,如果创建了一个CGColorSpace对象,则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。也可以使用Core Foundation的CFRetain和CFRelease。注意不能传递NULL值给这些函数