简述
Quartz 2D的API是纯C语言的,它是一个二维绘图引擎,同时支持iOS和Mac系统。Quartz2D的API来自于Core Graphics框架,数据类型和函数基本都以CG作为前缀。通常,我们可以使用系统提供的控件去完成大部分UI,但是有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,类似自定义控件。其实,iOS中大部分控件的内容都是通过Quartz2D画出来的,因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)。
Quartz 2D 功能
- 绘制图形 : 线条\三角形\矩形\圆\弧等
- 绘制文字
- 绘制\生成图片(图像)
- 读取\生成PDF
- 截图\裁剪图片
- 自定义UI控件
- … …
图形上下文 (Graphics Context)
翻译自官方文档
图形上下文表示一个绘图目的地。它包含绘图参数和绘图系统执行任何后续绘图命令所需的所有设备特定信息。图形上下文定义了基本的绘图属性,例如绘图时使用的颜色,剪裁区域,线宽和样式信息,字体信息,合成选项等等。
您可以通过使用Quartz上下文创建功能或通过使用由iOS OS中的某个Mac OS X框架或UIKit框架提供的更高级功能来获取图形上下文。Quartz提供各种风格的Quartz图形上下文的功能,包括位图和PDF,您可以使用它来创建自定义内容。
图形上下文在代码中由数据类型表示CGContextRef
,这是一种不透明的数据类型。获取图形上下文后,可以使用Quartz 2D函数绘制上下文,在上下文上执行操作(例如转换),并更改图形状态参数(如线宽和填充颜色)。
Quartz2D提供的Graphics Context类型:
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context
Printer Graphics Context
Quartz 2D自定义View
1、Quartz2D自定义view
首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去。
其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面。
自定义view的步骤:
(1)新建一个类,继承自UIView
(2)实现 -(void)drawRect:(CGRect)rect
方法,然后在这个方法中做以下操作:
(a)取得跟当前view相关联的图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
(b)绘制相应的图形内容
Demo
1. 绘制基本线段
- (void)drawRect:(CGRect)rect {
//绘制直线
[self drewSegment];
//绘制曲线
[self drewCurve];
}
//绘制简单线段
-(void)drewSegment{
//获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//绘制
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(50, 50)];
[path addLineToPoint:CGPointMake(150, 300)];
//线条宽度
CGContextSetLineWidth(context, 5);
//线条颜色
[[UIColor cyanColor] set];
//添加到上下文
CGContextAddPath(context, path.CGPath);
//渲染到view上
CGContextStrokePath(context);
}
//绘制曲线
-(void)drewCurve{
//获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//绘制曲线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 300)];
[path addQuadCurveToPoint:CGPointMake(200, 300) controlPoint:CGPointMake(50, 50)];
//线条宽度
CGContextSetLineWidth(context, 10);
//线条颜色
[[UIColor cyanColor] set];
//添加到上下文
CGContextAddPath(context, path.CGPath);
//渲染到view上
CGContextStrokePath(context);
}
2. 绘制基本图形
-(void)drawRect:(CGRect)rect{
//画矩形
// [self drawRectangle];
//画圆角矩形
// [self drawRoundedRect];
//画椭圆
// [self drawOval];
//画弧
// [self drawArc:rect];
//画扇形
[self drawSector:rect];
}
//画矩形
-(void)drawRectangle{
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
CGContextAddPath(context, path.CGPath);
CGContextStrokePath(context);
}
//画圆角矩形
-(void)drawRoundedRect{
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) cornerRadius:30];
//以下方法底层会调用上面的绘制方式
[path stroke];
}
//画椭圆
-(void)drawOval{
//椭圆
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 200, 200, 80)];
//圆
// UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 150, 100, 100)];
[path stroke];
}
//画弧
-(void)drawArc:(CGRect)rect{
//圆点
CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
//半径
CGFloat radius = rect.size.width * 0.5 - 10;
/**
Description
@param CGPoint 圆心
radius 半径
startAngle 起始角度
endAngle 终止角度
clockwise YES=顺时针 NO=逆时针
*/
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];
[path stroke];
}
//画扇形
-(void)drawSector:(CGRect)rect{
//圆点
CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
//半径
CGFloat radius = rect.size.width * 0.5 - 10;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];
//闭合曲线
[path addLineToPoint:center];
// [path closePath];
//
// [path stroke];
//用fill的话自动闭合曲线
[path fill];
}
3. 绘制饼图
-(void)drawRect:(CGRect)rect{
//数据
NSArray *dataArray = @[@33,@33,@33];
//绘图信息
CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
CGFloat radius = rect.size.width * 0.5 - 10;
CGFloat startAngle = 0;
CGFloat angle = 0;
CGFloat endAngle = 0;
for (NSNumber *num in dataArray ) {
startAngle = endAngle;
angle = num.intValue/99.0 * 2 * M_PI;
endAngle = startAngle + angle;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
[[self randomColor] set];
[path addLineToPoint:center];
[path fill];
}
}
-(void)reDraw{
//重绘
[self setNeedsDisplay];
}
//随机颜色
-(UIColor *)randomColor{
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.0];
}
4. 绘制带水印图
使用图片上下文,并不是在View上绘制
- (void)viewDidLoad {
[super viewDidLoad];
//加载图片
UIImage *image = [UIImage imageNamed:@"Logo"];
//开启上下文 参数2:不透明度
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//绘制图片到上下文
[image drawAtPoint:CGPointMake(0, 0)];
//绘制文字到上下文
NSString *str =@"Kinken_Yuen";
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSFontAttributeName] = [UIFont systemFontOfSize:30];
dict[NSForegroundColorAttributeName] = [UIColor whiteColor];
[str drawAtPoint:CGPointMake(450, 550) withAttributes:dict];
//从上下文获取新的图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//显示
self.imageView.image = newImage;
}
上下文大小是图片大小640x629,添加文字时的坐标点所以比较大
5. 圆形裁剪图片
- (void)viewDidLoad {
[super viewDidLoad];
//加载图片
UIImage *image = [UIImage imageNamed:@"logo"];
//开启图片上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//添加圆形裁剪区域
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[path addClip];
//将图片绘制到上下文中(超出裁剪区域不显示)
[image drawAtPoint:CGPointZero];
//从上下文取出图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//显示图片
self.imageView.image = newImage;
}
6. 圆形裁剪带边框图片
@implementation UIImage (ClipImage)
+ (UIImage *)clipImageWithBorder:(UIImage *)image withColor:(UIColor *)color borderWith:(CGFloat)width{
//图片上下文尺寸
CGSize size = CGSizeMake(image.size.width + 2 * width, image.size.height + 2 * width);
//开启图片上下文
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
//添加大圆边框
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.height)];
[color set];
[path fill];
//添加圆形裁剪区域
UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(width, width, image.size.width, image.size.height)];
[clipPath addClip];
//图片绘制到上下文
[image drawAtPoint:CGPointMake(width, width)];
//拿到照片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
return newImage;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:@"logo"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage clipImageWithBorder:image withColor:[UIColor cyanColor] borderWith:5]];
imageView.frame = CGRectMake(80, 20, image.size.width, image.size.height);
[self.view addSubview:imageView];
}
7. 简单截屏
- (IBAction)screenShot {
//开启view大小的图形上下文
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0);
//用绘制方式不能将view的layer显示到上下文,必须用渲染方式
// [self.view.layer drawInContext:UIGraphicsGetCurrentContext()];
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
//图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//将图片转成二进制数据保存到桌面
NSData *data = UIImagePNGRepresentation(newImage);
[data writeToFile:@"/Users/kinken_yuen/Desktop/logo.png" atomically:YES];
}
8. 图片局部截取
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;
/*手势起始点*/
@property(nonatomic,assign)CGPoint startP;
/*裁剪用的UIView*/
@property(nonatomic,weak)UIView *clipV;
@end
@implementation ViewController
-(UIView *)clipV{
if (_clipV == nil) {
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor lightGrayColor];
view.alpha = 0.3;
_clipV = view;
//个人感觉把截图的区域添加到imageV更合乎常理
[self.imageV addSubview:view];
}
return _clipV;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.imageV.userInteractionEnabled = YES;
}
- (IBAction)dragGestureOnPicture:(UIPanGestureRecognizer *)sender {
//记录起始点
CGPoint cur = [sender locationInView:self.imageV];
if (sender.state == UIGestureRecognizerStateBegan) {
self.startP = cur;
}else if(sender.state == UIGestureRecognizerStateChanged){ //拖拽手势
CGFloat x = self.startP.x;
CGFloat y = self.startP.y;
CGFloat w = [sender locationInView:self.imageV].x - x;
CGFloat h = [sender locationInView:self.imageV].y - y;
CGRect rect = CGRectMake(x, y, w, h);
//裁剪用的UIview
self.clipV.frame = rect;
}else if(sender.state ==UIGestureRecognizerStateEnded){
/*手势结束裁剪图片*/
//开启imageV大小的图片上下文
UIGraphicsBeginImageContextWithOptions(self.imageV.frame.size,NO, 0);
//添加裁剪区域,UIView的大小
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.clipV.frame];
[path addClip];
//渲染到图形上下文之前要把截取区域从imageView上移除
[self.clipV removeFromSuperview];
//将原始图片渲染到图片上下文,并按裁剪区域截取
[self.imageV.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//更新imageView
self.imageV.image = newImage;
}
}
@end
注意点
- 在drawRect:方法中取得与自定义view相关联的图形上下文
- drawRect:调用时机是在view第一次显示到屏幕时
- 手动调用drawRect:方法时,系统是不会给你创建跟view相关联的上下文,需要重绘制的时候应该调用setNeedsDisplay方法,此方法是底层需要刷新屏幕时调用drawRect:方法
- view内部有一个layer属性,drawRect:方法取得的是Layer Graphics Context,实质是绘制到view的layer上