引言:Quartz 2D绘图的核心API是CGContextRef,该API专门用于绘制各种图形。Quartz 2D是一个二维图形绘制引擎,它支持iOS环境和Mac OS X环境,为开发者会提供了很多的方便,它在绘图上功能是非常强大的,如 基于路径的绘图、透明度、阴影、颜色管理、反锯齿、PDF文档生成和PDF元数据访问 等。Quartz 2DAPI作为Core Graphics框架的一部分,因此其中的很多数据类型和方法都是以CG开头的。会经常见到Quartz 2D(Quartz)和Core Graphics两个术语交互使用。
学习Quartz 2D之前首先先来掌握几个基础知识。
1.图形上下文(Graphics Context)——绘制目标
1)Graphics Context是一个数据类型(CGContextRef),封装了Quartz绘制图像到输出设备的信息。 输出设备可以是PDF文件、Bitmap、Layer、打印机或者显示器的窗口上
2)Quartz中所有的 对象都是绘制到一个Graphics Context中
3)当用Quartz绘图时,所有设备相关的特性都包含在Graphics Context中。换句话说,我们可以简单地给Quartz绘图序列指定不同的Graphics Context,就可将相同的图像绘制到不同的设备上。而不需要任何设备相关的计算,这些都由Quartz替我们完成
2.Quartz 2D坐标系
1) Quartz中默认的坐标系统是:原点(0, 0)在左下角。 沿着X轴从左到右坐标值逐渐增大;沿着Y轴从下到上坐标值逐渐增大
2)有一些技术在设置它们的graphics context时使用了不同于Quartz的默认坐标系统。最常见的一种修改的坐标系统是原点位于左上角,而沿着Y轴从上到下坐标值逐渐增大。例如: UIView中的UIGraphicsGetCurrentContext方法返回的图形上下文就是用的是这种坐标系(坐标系统的原点位于左上角)
3.UIKit的坐标系
1) 原点(0,0)在屏幕的左上角,X轴向右正向延伸,Y轴向下正向延伸
2)iOS的像素分辨率会随设备的硬件而变化,iPhone4第一次引入了视网膜屏幕,像素分辨率为960* 640,刚好是前一代iPod和iPhone像素分辨率( 480* 320)的两倍
3)在绘图时,需要使用“点”的概念来思考问题,而不是像素。也就是说在点坐标系中绘图,不是硬件的像素坐标系
4)虽然这些设备的像素分辨率不同,但用到的坐标系保持不变(以点为单位)。在iPhone4上,一个点会用2像素宽度来绘制
提示:如果绘图的上下文,是使用UIGraphicsGetCurrentContext或者其他以UI开头的方法获取到的,在绘图时无需进行坐标转换
4.Quartz 2D的绘图顺序
后面绘制的图形,会覆盖先前绘制的图形。如下图所示:
使用Quartz 2D绘图的关键步骤有两步:
1.获取CGContextRef;
2.调用CGContextRef的方法进行绘图。
不同场景下获取CGContextRef的方式各不相同,下面介绍iOS开发中最常见的场景下如何获取CGContextRef.
(1)自定义UIView获取CGContextRef
开发自定义UIView的方法是,开发一个集成UIView的子类,并重写UIView的drawRect:方法,当该UIVie每次显示出来时,或该UIView的内容需要更新时,系统都会自动调用UIView的drawRect:方法。在调用UIView的drawRect:方法之前系统会自动配置绘图环境,因此程序只要通过如下函数即可获取CGContextRef绘图API:
CGContextRef context = UIGraphicsGetCurrentContext();
(2)创建位图时获取CGContextRef
如果需要在创建位图时获取CGContextRef,那么程序需要先调用UIGraphicsBeginImageContext()函数来创建内存中的图片。然后调用UIGraphicsGetCurrentContext()获取绘图的CGContextRef。
Quartz 2D时面向过程的API,Quartz 2D提供了大量函数来完成绘图。
现在通过一个案例来熟悉一下Quartz 2D的使用过程,以及常用的函数使用。首先为UIView创建一个DemoView类,本案例中所有的绘图操作都是在DemoView类中进行实现的。
#import "DemoView.h" @implementation DemoView - (void)drawRect:(CGRect)rect { // 获取绘图的上下文 CGContextRef context = UIGraphicsGetCurrentContext(); //1.绘制三角形 [self drawTriangle:context]; //2.绘制矩形 [self drawRectangle:context]; } //绘制三角形 -(void)drawTriangle:(CGContextRef)context { //2.添加绘图路径 CGContextMoveToPoint(context, 100, 100); CGContextAddLineToPoint(context, 200, 100); CGContextAddLineToPoint(context, 150, 200); CGContextAddLineToPoint(context, 100, 100); //3.设置绘图的属性 CGFloat myColor[4] = {1.0,0.0,0.0,1.0}; //设置描边的颜色 CGContextSetStrokeColor(context, myColor); CGFloat myColor1[4]= {0.0,1.0,0.0,1.0}; //设置填充的颜色 CGContextSetFillColor(context, myColor1); //设置线宽 CGContextSetLineWidth(context, 5.0); //设置线的类型:虚线————注意:如果对上下文进行了修改之后的所有连线的类型都默认为虚线啦!其他属性也一并如此。 CGFloat dash[2] = {1.0,2.0}; CGContextSetLineDash(context, 0, dash, 2); //设置连接点的类型 /* enum CGLineJoin { kCGLineJoinMiter, 连接处为尖角形状 kCGLineJoinRound, 连接处为圆角形状 kCGLineJoinBevel 连接处为平角形状 }; */ CGContextSetLineJoin(context, kCGLineJoinRound); //4.绘图 CGContextDrawPath(context, kCGPathFillStroke); } //绘制矩形 -(void)drawRectangle:(CGContextRef)context { //添加一个矩形 CGContextAddRect(context, CGRectMake(50, 50, 50, 50)); //添加一个圆形 CGContextAddEllipseInRect(context, CGRectMake(10, 100, 50, 50)); //添加一个椭圆 CGContextAddEllipseInRect(context, CGRectMake(200, 100, 100, 50)); //设置绘图属性 CGFloat myColor[4] = {1.0,0.0,0.0,1.0}; //设置描边颜色 CGContextSetStrokeColor(context, myColor); CGFloat myColor1[4] = {0.0,1.0,0.0,1.0}; //设置填充颜色 CGContextSetFillColor(context, myColor1); //绘图 CGContextDrawPath(context, kCGPathFillStroke); } @end
运行结果如下图:
Quartz 2D绘制的线条默认时实线的。上图中使用的却是点线,关于点线模式的设置我做多下详细的介绍:
如果需要创建点线可调用CGContextRef的CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);该函数的第3个参数是点线模式的关键,该参数是一个CGFloat型数组(第4个参数通用用于指定该数组的长度),每个CGFloat值依次控制点线的实现长度、间距。比如该参数如下:
1)- {2,3}:代表长为2的实线、距离为3的间距、长为2的实线、距离为3的间距......这种点线模式。
2)- {2,3,1}:代表长为2的实线、距离为3的间距、长为1的实线、距离为2的间距、长度为3的实线、距离为1的间距......这种点线模式。
3)- {5,3,1,2}:代表长为5的实线、距离为3的间距、长为1的实线、距离为2的间距、长为5的实线、距离为3的间距、长为1的实线、距离为2的间距.....这种点线模式。
该方法的第2个参数用于指定点线的相位,该参数将会与第3个参数协同起作用,比如如下参数组合。
- phase=1,lengths={2,3}:代表长为2的实线、距离为3的间距、长为2的实线、距离为3的间距。但开始绘制起点时只绘制长度为1的实线,因为phase为1就是控制该点线“移动”1个点。
- phase=3,lengths={5,3,1,2}:代表长为5的实线、距离为3的间距、长为1的实线、距离为2的间距、长为5的实线、距离为3的间距、长为1的实线、距离为2的间距。但开始绘制起点时只绘制长度为2的实线,因为phase为3就是控制该点线“移动”3个点。
三、CGContextRef实现文字、图片、基于路径的图形绘制
前面说到CGContextRef不但能提供绘制基本图形的功能,还可以提供绘制文字、图片、基于路径的图形的绘制。下面来看下对这三种的绘制时Quartz 2D是如何实现的。
1.使用路径
使用路径的步骤如下:
1.调用CGContextBeginPath函数开始定义路径
2.调用各种子函数添加路径
3.如果路径添加完成,调用CGContextClosePath函数关闭路径。
4.调用CGContextDrawPath(),CGContextEOFillPath(),CGContextFillPath()或CGContextStrokePath()函数来填充路径或绘制路径边框即可。在这些方法中,第一个方法可以替换后面的几个方法,第一个方法指定使用特定的模式来绘制图形。 它支持如下几种绘制方式。
- kCGPathFill:指定填充路径。相当于CGContextFillPath()函数。
- kCGPathEOFill:指定采用even-odd模式填充路径。相当于CGContextEOFillPath()函数。
- kCGPathStroke:指定只绘制路径。相当于CGContextStrkePath()函数。
-kCGPathFillStroke:同时绘制路径、也填充路径。
- kCGPathEOFillStroke:即绘制路径,也采用even-odd模式填充路径。
2.绘制文字
使用CGContextRef绘制文本的步骤如下:
1.获取绘图的CGContextRef。
2.设置绘制文本的相关属性,例如:绘制文本所用的绘制方式、字体大小、字体名称等。
3.如果只是绘制不需要进行变换的文本,直接调用NSString的drawAtPoint:withAttributes:、drawInAttributes:withFont:等方法绘制即可。如果需要对绘制的文本进行变换,则需要先调用CGContextSetTextMatrix()函数设置变换矩阵,在调用CGContextShowTextAtPoint()方法绘制文本。
3.绘制位图
为了绘制位图,UIImage本身已经提供了如下方法。
(1)- drawAtPoint:将该图片本身绘制到当前绘图CGContextRef的指定点。调用该方法必须传入一个CGPoint参数,指定该图片的绘制点。
(2)- drawAtPoint:blendMode:alpha:属于前一个方法的增强版,它可以制定绘制图片的叠加模式和透明度。
(3)- drawInRect:将该图片本身绘制到当前绘图CGContextRef的指定区域内。调用该方法必须传入一个CGRect参数,指定该图片的绘制区域。
4.设置阴影
CGContextRef为设置图形阴影提供了如下两个参数:
(1)- void CGContextSetShadow(CGContextRef context, CGSize offset,CGFloat blur):该函数设置阴影在X、Y方向上的偏移,并设置阴影的模糊程度。该函数的offset包含两个CGFloat值,第1个CGFloat值控制阴影在X方向的偏移,如果该值为正,则向右偏移,否则向左偏移;第2个CGFloat值控制阴影在Y方向的偏移,如果该值为正,则乡下偏移,否则向上偏移。最后一个blur参数控制阴影的模糊程度,如果参数为1,表明阴影几乎不模糊,blur参数越大,阴影越模糊。
示例: CGContextSetShadow(context, CGSizeMake(-8, -6), 5);
(2)- void CGContextSetShadowWithColor(CGContextRef context, CGSize offset,CGFloat blur, CGColorRef color):该函数与前一个函数的功能基本相似,只是该函数多了一个属性用于设置阴影颜色。
示例: CGContextSetShadowWithColor(context, CGSizeMake(-8, -6), 5, [[UIColor redColor]CGColor]);
现在通过一个案例代码,来使用以上的方法实现绘制图片、文字、使用路径绘制图片。不过此处绘制图片却没有使用提供的方法,后面说道在内存中绘制位图时再详解介绍。
#import "DemoView.h" @implementation DemoView - (void)drawRect:(CGRect)rect { // 获取绘图的上下文 CGContextRef context = UIGraphicsGetCurrentContext(); //3.通过路径的方式创建三角形 [self drawTriangleByPath:context]; //4.绘制文字 [self drawString:context]; //5.绘制图片 [self drawImage]; } -(void)drawImage { UIImage *image = [UIImage imageNamed:@"6.jpg"]; //将该图片本身绘制到当前绘图CGContextRef的指定区域中。 [image drawInRect:CGRectMake(100, 300, 200, 150)]; //将该图片本身绘制到当前回去CGContextRef的指定点 [image drawAtPoint:CGPointMake(10, 450)]; } -(void)drawString:(CGContextRef)context { NSString *string = @"hello world"; //设置使用描边模式绘制文字 CGContextSetTextDrawingMode(context, kCGTextStroke); //将文字本身绘制到当前CGContextRef的指定点 [string drawAtPoint:CGPointMake(100, 250) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSForegroundColorAttributeName:[UIColor blueColor]}]; //设置使用填充、描边绘制文字 CGContextSetTextDrawingMode(context, kCGTextFillStroke); //将文本本身绘制到当前CGContextRef的指定区域中 [string drawWithRect:CGRectMake(200, 250, 70, 80) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSForegroundColorAttributeName:[UIColor blueColor]} context:nil]; } -(void)drawTriangleByPath:(CGContextRef)context { //创建路径 CGMutablePathRef path = CGPathCreateMutable(); //向路径中添加图形 CGPathMoveToPoint(path, NULL, 20, 200);//创建起点 CGPathAddLineToPoint(path, NULL, 100, 300); CGPathAddLineToPoint(path, NULL, 60, 400); //将path添加到上下文 CGContextAddPath(context, path); //闭合路径 CGContextClosePath(context); //设置绘图的属性 //设置描边颜色 [[UIColor redColor]setStroke]; //设置填充颜色 [[UIColor greenColor]setFill]; //同时设置填充颜色,又能设置描边颜色 // [[UIColor whiteColor]set]; //使用默认的阴影颜色,阴影向左上角投影,模糊度为5 // CGContextSetShadow(context, CGSizeMake(-8, -6), 5); CGContextSetShadowWithColor(context, CGSizeMake(-8, -6), 5, [[UIColor redColor]CGColor]); //绘图 CGContextDrawPath(context, kCGPathFillStroke); } @end
运行效果图,如下所示:
经过两个案例,细心地朋友可能发现了这样的问题:在案例1中对三角形进行设置了线的类型,设置为点线模式,但是矩形的线的类型没有设置但同样变成了点线模式。在案例2中对三角形设置了阴影模式,但是图片、文字也均带有了阴影。如何解决?
解决办法,分为两步走:
1.对上下文进行操作之前,使用 CGContextSaveGState(CGContextRef c)保存当前上下文状态
2.对上下文进行操作之后,使用 CGContextRestoreGState(CGContextRef c)可以恢复之前保存的上下文状态
前面介绍的都是 通过扩展UIView、重写drawRect:方法进行绘图,这种绘图方式是直接在UIView控件上绘制所有的图形 ——由于每次该控件显示出来时,drawRect:方法都会被调用,这意味着每次该控件显示出来时,程序都需要重绘所有的图形,很明显,这种方式的性能并不好。除此之外,总有些时候需要在内存中绘制图片,这样既可导出到手机本地,也可上传到网络上。
在内存中绘图的步骤如下:
(1)调用UIGraphicsBeginImageContext(CGSize size)函数准备绘图环境。
(2)调用UIGraphiceGetCurrentContext()函数获取绘图CGContextRef
(3)用前面介绍的绘制集合图形、使用路径等方式进行绘图。
(4)调用UIGraphicsGetImageFromCurrentImageContext()函数获取当前绘制的图形,该方法返回一个UIImage对象。
(5)调用UIGraphicsEndImageContext()函数结束绘图,并关闭绘图环境。
#import "ViewController.h" @interface ViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate> @property (weak, nonatomic) IBOutlet UIImageView *imageView; @property(strong,nonatomic)UIImage *image; @property (weak, nonatomic) IBOutlet UITextField *textField; @end @implementation ViewController - (IBAction)drawClicked:(UIButton *)sender { //开始图形绘制的上下文 UIGraphicsBeginImageContext(self.imageView.frame.size); //获取图形绘制的上下文 CGContextRef context = UIGraphicsGetCurrentContext(); //绘制矩形 CGContextAddRect(context, CGRectMake(100, 100, 100, 100)); //设置描边、填充颜色 [[UIColor redColor]set]; //绘制图形 CGContextDrawPath(context, kCGPathFillStroke); //从图形绘制上下文获取图片 self.image = UIGraphicsGetImageFromCurrentImageContext(); //结束图形绘制的上下文 UIGraphicsEndImageContext(); //设置显示图片 [self.imageView setImage:self.image]; } - (IBAction)waterMarkClicked:(UIButton *)sender { //开始图形绘制的上下文 UIGraphicsBeginImageContext(self.imageView.frame.size); //先画图片 // self.image = [UIImage imageNamed:@"6.jpg"]; [self.image drawInRect:self.imageView.bounds]; //再画水印 NSString *string = self.textField.text; [string drawAtPoint:CGPointMake(100, 100) withAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor],NSFontAttributeName:[UIFont systemFontOfSize:25]}]; //从当前上下文获取图片 self.image = UIGraphicsGetImageFromCurrentImageContext(); //结束图形绘制的上下文 UIGraphicsEndImageContext(); //显示带水印的图片 self.imageView.image = self.image; } - (IBAction)saveClicked:(UIButton *)sender { //保存图片到外部设备 /* //设置图片保存的路径 NSString *doucuments = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]; NSString *fileName = [doucuments stringByAppendingPathComponent:@"watermark.png"]; //获取图片中的data NSData *imageData = UIImagePNGRepresentation(self.image); //保存图片 [imageData writeToFile:fileName atomically:YES]; NSLog(@"%@",NSHomeDirectory()); */ //保存图片到相册 UIImageWriteToSavedPhotosAlbum(self.image, nil, nil, nil); } - (IBAction)photoSelect:(UIButton *)sender { //打开相册 UIImagePickerController *imagePicker = [[UIImagePickerController alloc]init]; //设置图片的来源 imagePicker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; //设置代理 imagePicker.delegate = self; //使用模态窗口的方式显示相册 [self presentViewController:imagePicker animated:YES completion:nil]; } #pragma mark - 实现UIImagePickerControllerDelegate代理方法 -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { NSLog(@"%@",info); //通过字典方式获取图片 self.image = [info objectForKey:UIImagePickerControllerOriginalImage]; [self.imageView setImage:self.image]; //关闭模态窗口 [picker dismissViewControllerAnimated:YES completion:nil]; } - (void)viewDidLoad { [super viewDidLoad]; } @end
运行效果图,如下图:
前面介绍的都是使用一种颜色来填充区域,除此之外,Quartz 2D还允许使用颜色渐变、位图来填充指定区域。
Quartz 2D为我们提供了两种渐变填充的函数,分别是: 线性渐变填充、圆形径向渐变填充。
- void CGContextDrawRadialGradient(CGContextRef context,GradientRef gradient, CGPoint startCenter, CGFloat startRadius,CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options),参数详解如下:
/* 参数2:gradient参数代表渐变对象,
参数3:startCenter参数设置起始圆的圆心,
参数4:startRadius设置起始圆的半径,
参数5:endCenter参数代表结束圆的圆心
参数6:endRadius代表结束圆的半径
参数7:options可支持kCGGradientDrawsBeforeStartLocation(可扩展填充起点之前的区域)或kCGGradientDrawsAfterEndLocation(扩展填充结束点之后的区域)*/
- void CGContextDrawLinearGradient(CGContextRef context,CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint,CGGradientDrawingOptions options):
/* 参数2:gradient参数代表渐变对象,
参数3:startPoint开始点坐标
参数4:endPoint结束点坐标
参数5: options可支持kCGGradientDrawsBeforeStartLocation(可扩展填充起点之前的区域)或kCGGradientDrawsAfterEndLocation(扩展填充结束点之后的区域)*/
废话不多说,直接上代码分析:
#import "DemoView.h" @implementation DemoView - (void)drawRect:(CGRect)rect { // 画渐变:线性渐变、径向渐变 //获取绘图的上下文 CGContextRef context = UIGraphicsGetCurrentContext(); //画线性渐变 // [self drawLinarGradient:context]; [self drawRadialGradient:context]; } -(void)drawRadialGradient:(CGContextRef)context { //2.创建渐变 //2.1创建颜色空间 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); //2.2设置开始颜色、结束时颜色 UIColor *startColor =[UIColor yellowColor]; const CGFloat *startColorCompents = CGColorGetComponents([startColor CGColor]); UIColor *endColor = [UIColor blackColor]; const CGFloat *endColorCompents = CGColorGetComponents([endColor CGColor]); CGFloat components[8] = {startColorCompents[0],startColorCompents[1], startColorCompents[2], startColorCompents[3], endColorCompents[0], endColorCompents[1], endColorCompents[2], endColorCompents[3]}; CGFloat locations[2] = {0.0,1.0}; CGGradientRef gradien = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2); /* 参数详解; 参数1:用于指定该渐变所使用的颜色空间(如:RGB,CMYK,Gray等颜色空间); 参数2:用于指定根据不同的颜色空间设置多种颜色 参数3:locations参数指定各颜色点得分布位置(如果将该参数指定为NULL,各颜色点将会均匀分布) 参数4:count参数指定该渐变包含多少种颜色 */ //3.绘画渐变效果(径向渐变) CGContextDrawRadialGradient(context, gradien, CGPointMake(200, 200), 50, CGPointMake(200, 200), 100, kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation); /* 参数2:gradient参数代表渐变对象, 参数3:startCenter参数设置起始圆的圆心, 参数4:startRadius设置起始圆的半径, 参数5:endCenter参数代表结束圆的圆心 参数6:endRadius代表结束圆的半径 参数7:options可支持kCGGradientDrawsBeforeStartLocation(可扩展填充起点之前的区域)或kCGGradientDrawsAfterEndLocation(扩展填充结束点之后的区域) */ //4.清理工作 CGColorSpaceRelease(colorSpace); CGGradientRelease(gradien); } -(void)drawLinarGradient:(CGContextRef)context { //2.创建渐变 //2.1创建颜色空间 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); //2.2设置开始颜色、结束时颜色 UIColor *startColor =[UIColor redColor]; const CGFloat *startColorCompents = CGColorGetComponents([startColor CGColor]); UIColor *endColor = [UIColor blueColor]; const CGFloat *endColorCompents = CGColorGetComponents([endColor CGColor]); CGFloat components[8] = {startColorCompents[0],startColorCompents[1], startColorCompents[2], startColorCompents[3], endColorCompents[0], endColorCompents[1], endColorCompents[2], endColorCompents[3]}; CGFloat locations[2] = {0.0,1.0}; CGGradientRef gradien = CGGradientCreateWithColorComponents(colorSpace, components,NULL, 2); //3.绘画渐变效果(线性) CGContextDrawLinearGradient(context, gradien, CGPointMake(100, 100), CGPointMake(200, 100),kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation); /* options可支持kCGGradientDrawsBeforeStartLocation(可扩展填充起点之前的区域)或kCGGradientDrawsAfterEndLocation(扩展填充结束点之后的区域) */ //4.清理工作 CGColorSpaceRelease(colorSpace); CGGradientRelease(gradien); } @end
运行效果图,如下所示:
PDF文档存储依赖于分辨率的向量图形、文本和位图,并用于程序的一系列指令中。一个PDF文档可以包含多页的图形和文本。PDF可用于创建跨平台、只读的文档,也可用于绘制依赖于分辨率的图形。Quartz为所有应用程序创建高保真的PDF文档,这些文档保留应用的绘制操作,如图下图所示。PDF文档的结果将通过系统的其它部分或第三方法的产品来有针对性地进行优化。Quartz创建的PDF文档在Preview和Acrobat中都能正确的显示。
案例1:创建pdf文档(创建并读取)
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UIWebView *webView; @end @implementation ViewController - (IBAction)showPDF:(id)sender { //设置pdf文件的路径 NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSLog(@"%@",documentPath); NSString *pdfFileName = [documentPath stringByAppendingPathComponent:@"face.pdf"]; //创建URL NSURL *url = [NSURL URLWithString:pdfFileName]; //创建request NSURLRequest *request = [NSURLRequest requestWithURL:url]; //在webView中显示 [self.webView loadRequest:request]; } - (void)viewDidLoad { [super viewDidLoad]; // [self createImagePDF]; } -(void)createImagePDF { //设置pdf文件的路径 NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSLog(@"%@",documentPath); NSString *pdfFileName = [documentPath stringByAppendingPathComponent:@"face.pdf"]; //开始pdf的绘图上下文 UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectMake(0, 0, 320, 480), nil); for(int i = 0; i < 9; i++) { //开始pdf新的一页 UIGraphicsBeginPDFPage(); //画图像 UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"%d.png",i]]; [image drawAtPoint:CGPointMake(320/2, 480/2)]; } //结束pdf的绘图上下文 UIGraphicsEndPDFContext(); } //创建pdf -(void)createPDF { //设置pdf文件的路径 NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSLog(@"%@",documentPath); NSString *pdfFileName = [documentPath stringByAppendingPathComponent:@"test.pdf"]; //开始pdf的绘图上下文 UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectMake(0, 0, 320, 480), nil); //获取当前的绘图上下文 CGContextRef context = UIGraphicsGetCurrentContext(); //开始pdf新的一页 UIGraphicsBeginPDFPage(); //画圆形 CGContextAddEllipseInRect(context, CGRectMake(100, 100, 100, 100)); //设置描边、填充颜色 [[UIColor redColor]set]; //将图形绘制到上下文中 CGContextDrawPath(context, kCGPathFillStroke); //画字符串 NSString *str = @"this is a test page."; [str drawAtPoint:CGPointMake(200, 200) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSForegroundColorAttributeName:[UIColor blueColor]}]; //结束pdf的绘图上下文 UIGraphicsEndPDFContext(); } @end
运行效果图,如下图所示:
以上案例中共实现三个功能:
(1)将图片绘制到pdf文档中去,
(2)将自定义的图形绘制到pdf文档中
(3)通过UIWebView控件进行显示pdf文档内容(这种方式只只用于图形类的内容,如果是电子书文本类的内容,则此方式不可实现)。
#import "PDFView.h" @interface PDFView()<UIActionSheetDelegate,UIAlertViewDelegate> { CGPDFDocumentRef _pdfDoc; size_t _pageNo;//当前页码 size_t _totalPage;//总的页数 } @end @implementation PDFView - (void)drawRect:(CGRect)rect { NSLog(@"重绘"); CGContextRef context = UIGraphicsGetCurrentContext(); //旋转坐标系 CGContextTranslateCTM(context, 80, self.frame.size.height-60); CGContextScaleCTM(context, 1, -1); //画页面 [self drawPDFPage:_pageNo context:context]; } //打开pdf文档 -(void)openPDF:(NSURL *)url { CFURLRef urlRef = (__bridge CFURLRef)url; _pdfDoc = CGPDFDocumentCreateWithURL(urlRef); _totalPage = CGPDFDocumentGetNumberOfPages(_pdfDoc); _pageNo = 1; } //显示pdf页面 -(void)drawPDFPage:(size_t)pageNo context:(CGContextRef)context { CGPDFPageRef pdfPage = CGPDFDocumentGetPage(_pdfDoc, pageNo); CGContextDrawPDFPage(context, pdfPage); } - (IBAction)prePage:(id)sender { if(_pageNo > 1) { _pageNo--; [self setNeedsDisplay]; } } - (IBAction)nextPage:(id)sender { if(_pageNo < _totalPage) { _pageNo++; [self setNeedsDisplay]; } } @end
运行效果图,如下所示: