在iOS中常用的绘图框架就是Quartz 2D,Quartz 2D是Core Graphics框架的一部分,Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。我们可以使用Quartz 2D API来实现许多功能,如基本路径的绘制、透明度、描影、绘制阴影、透明层、颜色管理、反锯齿、PDF文档生成和PDF元数据访问。在需要的时 候,Quartz 2D还可以借助图形硬件的功能。 Quartz 2D在UIKit中也有很好的封装和集成,我们日常开发时所用到的UIKit中的组件都是由Core Graphics进行绘制的。不仅如此,当我们引入UIKit框架时系统会自动引入Core Graphics框架,并且为了方便开发者使用在UIKit内部还对一些常用的绘图API进行了封装。
在iOS中绘图一般分为以下几个步骤:
1.获取绘图上下文2.创建并设置路径
3.将路径添加到上下文
4.设置上下文状态
5.绘制路径
6.释放路径
图形上下文CGContextRef代表图形输出设备(也就是绘制的位置),包含了绘制图形的一些设备信息,Quartz 2D中的所有对象最终都必须绘制到图形上下文。这样一来,我们在绘制图形时就不必关心具体的设备信息,统一了代码编写方式(在Quartz 2D中的绘图上下文可以是位图Bitmap、PDF、窗口Window、层Layer、打印对象Printer)。
知识补充:
1.Core Graphics是基于C语言的一套框架,开发时无法像使用Obj-C一样调用;
2.在Quartz 2D中凡是使用带有“Create”或者“Copy”关键字方法创建的对象,在使用后一定要使用对应的方法释放(由于这个框架基于C语言编写无法自动释放内存);
3.Quartz 2D是跨平台的,因此其中的方法中不能使用UIKit中的对象(UIKit只有iOS可用),例如用到的颜色只能用CGColorRef而不能用UIColor,但是UIKit中提供了对应的转换方法;
4.在C语言中枚举一般以“k”开头,由于Quartz 2D基于C语言开发,所以它也不例外(参数中很多枚举都是k开头的);
5.由于Quartz 2D是Core Graphics的一部分,所以API多数以CG开头;
6.在使用Quartz 2D绘图API中所有以“Ref”结尾对象,在声明时都不必声明为指针类型;
7.在使用Quartz 2D绘图API时,凡是“UI”开头的相关绘图函数,都是UIKit对Core Graphics的封装(主要为了简化绘图操作);
下面一起学习一下基本的操作:
在UIKit中默认已经为我们准备好了一个图形上下文对象,在UI控件的drawRect:方法中我们可以通过UIKit封装函数UIGraphicsGetCurrentContext()方法获得这个图形上下文,然后我们只要按照绘图步骤一步步执行即可。
drawRect注意事项:
-(void)drawLineOne{
//1.获得图形上下文
CGContextRef context=UIGraphicsGetCurrentContext();
//2.绘制路径(相当于前面创建路径并添加路径到图形上下文两步操作)
CGContextMoveToPoint(context, 20, 50);
CGContextAddLineToPoint(context, 20, 100);
CGContextAddLineToPoint(context, 300, 100);
//封闭路径:a.创建一条起点和终点的线,不推荐
//CGPathAddLineToPoint(path, nil, 20, 50);
//封闭路径:b.直接调用路径封闭方法.
CGContextClosePath(context);
//3.设置图形上下文属性
[[UIColor redColor]setStroke];//设置红色边框
[[UIColor greenColor]setFill];//设置绿色填充
//[[UIColor blueColor]set];//同时设置填充和边框色
//4.绘制路径
CGContextDrawPath(context, kCGPathFillStroke);
}
效果如下:
2:绘制矩形 方法有2种
//在下面的方法中还可以看到UIKit对绘图方法的封装,使用起来更加简单。这里绘制之前取得图形上下文对象传入.
-(void)drawRectWithContext:(CGContextRef)context{
//添加矩形对象
CGRect rect=CGRectMake(20, 50, 280.0, 50.0);
CGContextAddRect(context,rect);
//设置属性
[[UIColor blueColor]set];
//绘制
CGContextDrawPath(context, kCGPathFillStroke);
}
//绘制矩形(利用UIKit的封装方法)
-(void)drawRectByUIKit{
CGRect rect= CGRectMake(20, 150, 280.0, 50.0);
CGRect rect2=CGRectMake(20, 250, 280.0, 50.0);
//设置属性
[[UIColor yellowColor]set];
//绘制矩形,相当于创建对象、添加对象到上下文、绘制三个步骤
UIRectFill(rect);//绘制矩形(只有填充)
[[UIColor redColor]setStroke];
UIRectFrame(rect2);//绘制矩形(只有边框)
}
效果如下: (这里同时调用了2个方法)
3:绘制椭圆 ,弧形
//绘制椭圆 也可以使用绘制圆弧方法进行绘制
-(void)drawEllipse:(CGContextRef)context{
//添加对象,绘制椭圆(圆形)的过程也是先创建一个矩形
CGRect rect=CGRectMake(50, 50, 100.0, 100.0);
CGContextAddEllipseInRect(context, rect);
//设置属性
[[UIColor purpleColor]set];
//绘制
CGContextDrawPath(context, kCGPathFillStroke);
}
// 绘制弧形
-(void)drawArc:(CGContextRef)context{
/*
添加弧形对象
x:中心点x坐标
y:中心点y坐标
radius:半径
startAngle:起始弧度
endAngle:终止弧度
closewise:是否逆时针绘制,0则顺时针绘制
*/
CGContextAddArc(context, 200, 200, 50.0, 0.0, M_PI_2, 1);
//设置属性
[[UIColor yellowColor]set];
//绘制
CGContextDrawPath(context, kCGPathFillStroke);
}
效果图:
4:绘制贝塞尔曲线
要绘制规则图形在iOS中相当简单,但是不规则图形怎么绘制呢?此时就要利用路径。前面我们绘制了直线,它和曲线绘制都属于路径绘制。和直线绘制相比曲线绘制就要复杂一些,但是路径作为高级动画的基础又是我们必须掌握的,因此这里我们就一起来熟悉一下曲线绘制。在Quartz 2D中曲线绘制分为两种:二次贝塞尔曲线和三次贝塞尔曲线。二次曲线只有一个控制点,而三次曲线有两个控制点:
-(void)drawCurve:(CGContextRef)context{效果图:
//绘制曲线
CGContextMoveToPoint(context, 20, 100);//移动到起始位置
/*绘制二次贝塞尔曲线
c:图形上下文
cpx:控制点x坐标
cpy:控制点y坐标
x:结束点x坐标
y:结束点y坐标
当然,在iOS中两种曲线分别对应两种方法:
CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y);
CGContextAddCurveToPoint(context, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat
*/
CGContextAddQuadCurveToPoint(context, 160, 0, 300, 100);
CGContextMoveToPoint(context, 20, 300);
/*
绘制三次贝塞尔曲线
c:图形上下文
cp1x:第一个控制点x坐标
cp1y:第一个控制点y坐标
cp2x:第二个控制点x坐标
cp2y:第二个控制点y坐标
x:结束点x坐标
y:结束点y坐标
*/
CGContextAddCurveToPoint(context, 80, 100, 220, 400, 300, 200);
//设置图形上下文属性
[[UIColor yellowColor]setFill];
[[UIColor redColor]setStroke];
//绘制路径
CGContextDrawPath(context, kCGPathFillStroke);
}
5:文字绘制 ,图像绘制
#pragma mark 文字绘制 除了绘制图形还可以绘制文本内容。效果图:
-(void)drawText:(CGContextRef)context{
//绘制到指定的区域内容
NSString *str=@"One is always on a strange road, watching strange scenery and listening to strange music.
Then one day, you will find that the things you try hard to forget are already gone.
一个人总要走陌生的路,看陌生的风景,听陌生的歌,然后在某个不经意的瞬间,你会发现,原本是费尽心机想要忘记的事情真的就那么忘记了.";
CGRect rect= CGRectMake(20, 50, 280, 300);
UIFont *font=[UIFont systemFontOfSize:18];//设置字体
UIColor *color=[UIColor redColor];//字体颜色
NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];//段落样式
NSTextAlignment align=NSTextAlignmentLeft;//对齐方式
style.alignment=align;
//开始绘制文本
[str drawInRect:rect withAttributes:@{NSFontAttributeName:font,NSForegroundColorAttributeName:color,
NSParagraphStyleAttributeName:style}];
}
#pragma mark 图像绘制 Quartz 2D还可以将图像绘制到图形上下文。
-(void)drawImage:(CGContextRef)context{
//下面3中方法进行绘制
UIImage *image=[UIImage imageNamed:@"jt_03.png"];
//从某一点开始绘制
[image drawAtPoint:CGPointMake(10, 250)];
//绘制到指定的矩形中,注意如果大小不合适会会进行拉伸
//[image drawInRect:CGRectMake(10, 50, 300, 450)];
//平铺绘制
//[image drawAsPatternInRect:CGRectMake(0, 0, 320, 568)];
}
6:创建路径对象绘制之前的三角形
-(void)drawTriangle{效果图:
//1.取得图形上下文对象
CGContextRef context = UIGraphicsGetCurrentContext();
//2.创建路径对象.然后就绘制自己需要的路径,并进行添加即可
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 20, 50);//移动到指定位置(设置路径起点)
CGPathAddLineToPoint(path, nil, 20, 100);//绘制直线(从起始位置开始)
CGPathAddLineToPoint(path, nil, 300, 100);//绘制另外一条直线(从上一直线终点开始绘制)
//3.添加路径到图形上下文
CGContextAddPath(context, path);
//4.设置图形上下文状态属性
CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);//设置笔触颜色
CGContextSetRGBFillColor(context, 0, 1.0, 0, 1);//设置填充色
CGContextSetLineWidth(context, 2.0);//设置线条宽度
CGContextSetLineCap(context, kCGLineCapRound);//设置顶点样式,(20,50)和(300,100)是顶点
CGContextSetLineJoin(context, kCGLineJoinRound);//设置连接点样式,(20,100)是连接点
/*
设置线段样式
phase:虚线开始的位置
lengths:虚线长度间隔(例如下面的定义说明第一条线段长度8,然后间隔3重新绘制8点的长度线段,当然这个数组可以定义更多元素)
count:虚线数组元素个数
*/
CGFloat lengths[2] = { 18, 9 };
CGContextSetLineDash(context, 0, lengths, 2);
/*设置阴影
context:图形上下文
offset:偏移量
blur:模糊度
color:阴影颜色
*/
CGColorRef color = [UIColor grayColor].CGColor;//颜色转化,由于Quartz 2D跨平台,所以其中不能使用UIKit中的对象,但是UIkit提供了转化方法
CGContextSetShadowWithColor(context, CGSizeMake(2, 2), 0.8, color);
//5.绘制图像到指定图形上下文
/*
CGPathDrawingMode是填充方式,枚举类型
kCGPathFill:只有填充(非零缠绕数填充),不绘制边框
kCGPathEOFill:奇偶规则填充(多条路径交叉时,奇数交叉填充,偶交叉不填充)
kCGPathStroke:只有边框
kCGPathFillStroke:既有边框又有填充
kCGPathEOFillStroke:奇偶填充并绘制边框
*/
CGContextDrawPath(context, kCGPathFillStroke);//最后一个参数是填充类型
//6.释放对象
CGPathRelease(path);
}
7:添加水印效果
// 利用位图上下文添加水印效果
-(UIImage *)drawImageAtImageContextFromImageName:(NSString *)imageName{
//获得一个位图图形上下文
CGSize size=CGSizeMake(300, 188);//画布大小
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
//UIGraphicsBeginImageContext(size);
UIImage *image=[UIImage imageNamed:imageName];
//注意绘图的位置是相对于画布顶点而言.
[image drawInRect:CGRectMake(0, 0, 300, 188)];
//添加水印,其实就是添加文字到图片上,然后合成一张图片
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 200, 178);
CGContextAddLineToPoint(context, 270, 178);
[[UIColor redColor]setStroke];
CGContextSetLineWidth(context, 2);
CGContextDrawPath(context, kCGPathStroke);
NSString *str=@"Long Shi Hua";
[str drawInRect:CGRectMake(200, 158, 100, 30) withAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"Marker Felt" size:15],NSForegroundColorAttributeName:[UIColor redColor]}];
//返回绘制的新图形
UIImage *newImage=UIGraphicsGetImageFromCurrentImageContext();
//最后一定不要忘记关闭对应的上下文
UIGraphicsEndImageContext();
//保存图片
//NSData *data= UIImagePNGRepresentation(newImage);
//[data writeToFile:@"/Users/kenshincui/Desktop/myPic.png" atomically:YES];
return newImage;
//注意:上面这种方式绘制的图像除了可以显示在界面上还可以调用对应方法进行保存(代码注释中已经包含保存方法);除此之外这种方法相比在drawRect:方法中绘制图形效率更高,它不用每次展示时都调用所有图形绘制方法。
}
效果图:
8:绘制内容到pdf文档
绘制到PDF则要启用pdf图形上下文,PDF图形上下文的创建使用方式跟位图图形上下文是类似的,需要注意的一点就是绘制内容到PDF时需要创建分页,每页内容的开始都要调用一次IGraphicsBeginPDFPage();方法。下面的示例演示了文本绘制和图片绘制(其他图形绘制也是类似的):
//利用pdf图形上下文绘制内容到pdf文档
-(void)drawContentToPdfContext{
//沙盒路径(也就是我们应用程序文件运行的路径)
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path=[[paths firstObject] stringByAppendingPathComponent:@"myPDF.pdf"];
NSLog(@"%@",path);//进入沙盒路径就可以看到我们生成的pdf文档
//启用pdf图形上下文
/**
path:保存路径
bounds:pdf文档大小,如果设置为CGRectZero则使用默认值:612*792
pageInfo:页面设置,为nil则不设置任何信息
*/
UIGraphicsBeginPDFContextToFile(path,CGRectZero,[NSDictionary dictionaryWithObjectsAndKeys:@"Long Shi Hua",kCGPDFContextAuthor, nil]);
//由于pdf文档是分页的,所以首先要创建一页画布供我们绘制.
//注意: 创建PDF页面,每个页面的装载量是有限的,在默认的页面大小里面,可以装2张图片,所以添加了两张图片之后,需要新建一个页面.
UIGraphicsBeginPDFPage();
NSString *title=@"Welcome to Apple Support";
NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];
NSTextAlignment align=NSTextAlignmentCenter;
style.alignment=align;
[title drawInRect:CGRectMake(26, 20, 300, 50) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSParagraphStyleAttributeName:style}];
NSString *content=@"Learn about Apple products, view online manuals, get the latest downloads, and more. Connect with other Apple users, or get service, support, and professional advice from Apple.";
NSMutableParagraphStyle *style2=[[NSMutableParagraphStyle alloc]init];
style2.alignment=NSTextAlignmentLeft;
[content drawInRect:CGRectMake(26, 56, 300, 255) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor grayColor],NSParagraphStyleAttributeName:style2}];
UIImage *image=[UIImage imageNamed:@"jt_03"];
[image drawInRect:CGRectMake(316, 20, 290, 305)];
UIImage *image2=[UIImage imageNamed:@"jt_01"];
[image2 drawInRect:CGRectMake(6, 320, 600, 281)];
//创建新的一页继续绘制其他内容
UIGraphicsBeginPDFPage();
UIImage *image3=[UIImage imageNamed:@"jt_05"];
[image3 drawInRect:CGRectMake(6, 20, 600, 629)];
//结束pdf上下文
UIGraphicsEndPDFContext();
}
进入打印出来的沙盒路经就可以看到我们自己创建的pdf文档:点击桌面前往->前往文件夹->输入沙盒路径->点击前往.
9:绘制饼状图和树状图
//绘制饼状图效果图:
-(void)drawPinViewWithContext:(CGContextRef)context{
NSArray *data = @[@25,@25,@50];
// 1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.拼接路径
CGPoint center = CGPointMake(125, 125);
CGFloat radius = 120;
CGFloat startA = 0;
CGFloat angle = 0;
CGFloat endA = 0;
for (NSNumber *number in data) {
// 2.拼接路径
startA = endA;
angle = number.intValue / 100.0 * M_PI * 2;
endA = startA + angle;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
//连线到圆弧中心点
[path addLineToPoint:center];
//生成随机颜色
[[self randomColor] set];
// 把路径添加上下文
CGContextAddPath(ctx, path.CGPath);
// 渲染
CGContextFillPath(ctx);
}
}
- (UIColor *)randomColor
{
/*
颜色有两种表现形式 RGB RGBA
RGB 24
R,G,B每个颜色通道8位
8的二进制 255
R,G,B每个颜色取值 0 ~255
120 / 255.0
*/
CGFloat r = arc4random_uniform(256) / 255.0;
CGFloat g = arc4random_uniform(256) / 255.0;
CGFloat b = arc4random_uniform(256) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
//绘制树状图,输入上下文会绘制大小效果图:
-(void)drawBarViewWithContext:(CGContextRef)context contextRect:(CGRect)rect{
NSArray *data = @[@25,@25,@50];
int count = data.count;
CGFloat w = rect.size.width / (2 * count - 1);
CGFloat h = 0;
CGFloat x = 0;
CGFloat y = 0;
CGFloat viewH = rect.size.height;
// 1.获取上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
for (int i = 0; i < count; i++) {
h = viewH * [data[i] intValue] / 100.0;
x = 2 * w * i;
y = viewH - h;
// 2.拼接路径
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(x, y, w, h)];
// 3.添加路径到上下文
CGContextAddPath(ctx, path.CGPath);
[[self randomColor] set];
// 4.渲染
CGContextFillPath(ctx);
}
}
//触摸屏幕进行重新绘制
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self setNeedsDisplay];
}
10:模仿UIImageView,实现 view 中显示图片,并且触摸屏幕变换图片.
理解几点:
当在UIView子类中重写drawRect:方法时,iOS会自动准备好一个图形上下文,可以通过调用UIGraphicsGetCurrentContext()来获取. 只要一个UIView需要被刷新或者重绘,drawRect:方法就会被调用,所以drawRect:的调用频率很高. 需要注意的是:重绘时应该调用setNeedsDisplay,而不能直接调用drawRect:,setNeedsDisplay会自动调用drawRect:
//自定义一个 view
@interface LSHView : UIView
@property (nonatomic, strong) UIImage *image;
@end
@implementation LSHView
- (void)setImage:(UIImage *)image
{
_image = image;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
// Drawing code
[_image drawInRect:rect];
}
@end
//在ViewController.m导入自定义 view, 声明相应的 view.
@interface ViewController ()
@property(nonatomic,strong) LSHView *imageView;
@end
- (void)viewDidLoad {
[super viewDidLoad];
LSHView *view=[[LSHView alloc]initWithFrame:CGRectMake(10, 50, 300, 300)];
view.image=[UIImage imageNamed:@"1.jpg"];
[self.view addSubview:view];
self.imageView=view;
}
//原理:触摸屏幕,设置图片,调用setNeedsDisplay方法然后进行重新绘制图片.对于绘制进度条和动态改变字体大小,都可以使用相同原理进行操作.
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//随机生成数字1-6,显示不同的图片.
self.imageView.image=[UIImage imageNamed:[NSString stringWithFormat:@"%d.jpg",arc4random() % 6 +1]];
}