iOS開發(fā)之Quartz2D的介紹與使用詳解
一、前言
Quartz2D的API是純C語言的,它是一個二維繪圖引擎,同時支持iOS和Mac系統(tǒng)。Quartz2D的API來自于Core Graphics框架,數據類型和函數基本都以CG作為前綴。通常,我們可以使用系統(tǒng)提供的控件去完成大部分UI,但是有些UI界面極其復雜、而且比較個性化,用普通的UI控件無法實現,這時可以利用Quartz2D技術將控件內部的結構畫出來,類似自定義控件。其實,iOS中大部分控件的內容都是通過Quartz2D畫出來的,因此,Quartz2D在iOS開發(fā)中很重要的一個價值是:自定義view(自定義UI控件)。
Quartz 2D能完成的工作:
- 繪制圖形 : 線條\三角形\矩形\圓\弧等;
- 繪制文字;
- 繪制\生成圖片(圖像);
- 讀取\生成PDF;
- 截圖\裁剪圖片;
- 自定義UI控件;
- … …
二、圖形上下文(Graphics Context)
圖形上下文(Graphics Context):是一個CGContextRef類型的數據。
圖形上下文的作用:
(1)保存繪圖信息、繪圖狀態(tài)
(2)決定繪制的輸出目標(繪制到什么地方去?)
(輸出目標可以是PDF文件、Bitmap或者顯示器的窗口上)
相同的一套繪圖序列,指定不同的Graphics Context,就可將相同的圖像繪制到不同的目標上。
Quartz2D提供了以下幾種類型的Graphics Context:
(1)Bitmap Graphics Context
(2)PDF Graphics Context
(3)Window Graphics Context
(4)Layer Graphics Context
(5)Printer Graphics Context
將當前的上下文copy一份,保存到棧頂(那個棧叫做”圖形上下文棧”):
void CGContextSaveGState(CGContextRef c)
將棧頂的上下文出棧,替換掉當前的上下文(清空之前對于上下文設置):
void CGContextRestoreGState(CGContextRef c)
三、使用Quartz2D自定義View
1、Quartz2D自定義view
如何利用Quartz2D自定義view?(自定義UI控件)如何利用Quartz2D繪制東西到view上?
首先,得有圖形上下文,因為它能保存繪圖信息,并且決定著繪制到什么地方去。
其次,那個圖形上下文必須跟view相關聯(lián),才能將內容繪制到view上面。
自定義view的步驟:
(1)新建一個類,繼承自UIView
(2)實現- (void)drawRect:(CGRect)rect
方法,然后在這個方法中
(a)取得跟當前view相關聯(lián)的圖形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
(b)繪制相應的圖形內容
例如:畫1/4圓(扇形)
CGContextMoveToPoint(ctx, 100, 100); CGContextAddLineToPoint(ctx, 100, 150); CGContextAddArc(ctx, 100, 100, 50, -M_PI_2, M_PI, 1); CGContextClosePath(ctx); [[UIColor redColor] set];
(3)利用圖形上下文將繪制的所有內容渲染顯示到view上面
CGContextFillPath(ctx);
注:
//M_PI的含義:π //M_PI * 2的含義:2π //M_PI_2的含義:π/2 //M_PI / 2的含義:π/2 // 畫的圖形路徑 //bezierPathWithArcCenter:弧所在的圓心 //radius:圓的半徑 //startAngle:開始角度,圓的最右側為0度 //endAngle:截至角度,向下為正,向上為負. //clockwise:時針的方向,yes:順時針 no:逆時針 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:self.center radius:radius startAngle:startA endAngle:endA clockwise:NO];
完整代碼:
// // MyView.m // Quartz2DTest // // Created by 李峰峰 on 2017/2/6. // Copyright © 2017年 李峰峰. All rights reserved. // #import "MyView.h" @implementation MyView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor whiteColor];//設置背景為白色,為了便于觀察繪制后的效果 } return self; } - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextMoveToPoint(ctx, 100, 100); CGContextAddLineToPoint(ctx, 100, 150); CGContextAddArc(ctx, 100, 100, 50, -M_PI_2, M_PI, 1); CGContextClosePath(ctx); [[UIColor redColor] set]; CGContextFillPath(ctx); } @end
運行效果:
2、核心方法drawRect:
為什么要實現drawRect:方法才能繪圖到view上?
因為在drawRect:方法中才能取得跟view相關聯(lián)的圖形上下文
drawRect:方法在什么時候被調用?
當view第一次顯示到屏幕上時(被加到UIWindow上顯示出來)
調用view的setNeedsDisplay或者setNeedsDisplayInRect:時.
注意4點:
- 手動調用drawRect:方法,不會自動創(chuàng)建跟View相關聯(lián)的上下文。應該調用setNeedsDisplay方法,系統(tǒng)底層會自動調用drawRect,告訴系統(tǒng)重新繪制View.這樣,系統(tǒng)底層會自動創(chuàng)建跟View相關聯(lián)的上下文
- setNeedsDisplay底層會調用drawRect,并不是立馬調用的.只是設了一個調用的標志.調用時刻是等下一次屏幕刷新時才去調用drawRect。屏幕每一秒刷新30-60秒次,所以1秒調用drawRect方法大概30-60次,速度非常快哦
- view內部有個layer(圖層)屬性,drawRect:方法中取得的是一個Layer Graphics Context,因此,繪制的東西其實是繪制到view的layer上去了
- View之所以能顯示東西,完全是因為它內部的layer
3、Quartz2D繪圖的代碼步驟
第一步:獲得圖形上下文:
CGContextRef ctx = UIGraphicsGetCurrentContext();
第二步:拼接路徑(下面代碼是繪制一條線段):
CGContextMoveToPoint(ctx, 10, 10); CGContextAddLineToPoint(ctx, 100, 100);
第三步:繪制路徑:
CGContextStrokePath(ctx); // CGContextFillPath(ctx);
四、Quartz2D重要函數
1、常用拼接路徑函數
新建一個起點
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)
添加新的線段到某個點
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)
添加一個矩形
void CGContextAddRect(CGContextRef c, CGRect rect)
添加一個橢圓
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)
添加一個圓弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
2、常用繪制路徑函數
一般以CGContextDraw、CGContextStroke、CGContextFill開頭的函數,都是用來繪制路徑的。
Mode參數決定繪制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
繪制空心路徑
void CGContextStrokePath(CGContextRef c)
繪制實心路徑
void CGContextFillPath(CGContextRef c)
3、矩陣操作函數
利用矩陣操作,能讓繪制到上下文中的所有路徑一起發(fā)生變化。
縮放:
void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
旋轉:
void CGContextRotateCTM(CGContextRef c, CGFloat angle)
平移:
void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
4、其他常用函數
設置線段寬度
CGContextSetLineWidth(ctx, 10);
設置線段頭尾部的樣式
CGContextSetLineCap(ctx, kCGLineCapRound);
設置線段轉折點的樣式
CGContextSetLineJoin(ctx, kCGLineJoinRound);
設置顏色
CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);
五、Quartz2D的內存管理
關于Quartz2D內存管理,有以下原則:
(1)使用含有“Create”或“Copy”的函數創(chuàng)建的對象,使用完后必須釋放,否則將導致內存泄露。
(2)使用不含有“Create”或“Copy”的函數獲取的對象,則不需要釋放
(3)如果retain了一個對象,不再使用時,需要將其release掉。
(4)可以使用Quartz 2D的函數來指定retain和release一個對象。例如,如果創(chuàng)建了一個CGColorSpace對象,則使用函數CGColorSpaceRetain和CGColorSpaceRelease來retain和release對象。
(5)也可以使用Core Foundation的CFRetain和CFRelease。注意不能傳遞NULL值給這些函數。
六、Quartz2D使用案例
1、畫矩形、正方形
- (void)drawRect:(CGRect)rect { //1.獲取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); //2.描述路徑 UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 200)]; //3.把路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); [[UIColor redColor] set];// 路徑的顏色 //4.把上下文的內容渲染到View的layer. //CGContextStrokePath(ctx);// 描邊路徑 CGContextFillPath(ctx);// 填充路徑 }
運行效果:
2、畫扇形
除上面“二、使用Quartz2D自定義View”中的方法外,也可以使用OC中自帶畫圖方法實現,如下:
- (void)drawRect:(CGRect)rect { CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5); CGFloat radius = rect.size.width * 0.5 - 10; CGFloat startA = 0; CGFloat endA = -M_PI_2; // 畫弧的路徑 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:NO]; // 添加一根線到圓心 [path addLineToPoint:center]; // 閉合路徑 [path closePath]; // 路徑顏色 [[UIColor redColor] set]; // 填充路徑 [path fill]; // 描邊路徑 //[path stroke]; }
運行效果:
注:
判斷一個點是否在一個矩形框內
CGRectContainsPoint(rect,point);//判斷point這個點是否在rect這個矩形框內
3、畫圓形
- (void)drawRect:(CGRect)rect { //1.獲取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); //2.描述路徑 // cornerRadius:圓角半徑。矩形的寬高都為200,如果圓角為100,那么兩個角之間弧線上任意一點到矩形中心的距離都為100,所以為圓形 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 200) cornerRadius:100]; //3.把路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); [[UIColor redColor] set];// 路徑的顏色 //4.把上下文的內容渲染到View的layer. // CGContextStrokePath(ctx);// 描邊路徑 CGContextFillPath(ctx);// 填充路徑 }
運行效果:
4、畫圓角矩形
- (void)drawRect:(CGRect)rect { //1.獲取上下文 CGContextRef ctx = UIGraphicsGetCurrentContext(); //2.描述路徑 // cornerRadius:圓角半徑。 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 200) cornerRadius:50]; //3.把路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); [[UIColor redColor] set];// 路徑的顏色 //4.把上下文的內容渲染到View的layer. CGContextStrokePath(ctx);// 描邊路徑 //CGContextFillPath(ctx);// 填充路徑 }
運行效果:
5、畫直線
- (void)drawRect:(CGRect)rect { //1.獲取跟View相關聯(lián)的上下文(uigraphics開頭) CGContextRef ctx = UIGraphicsGetCurrentContext(); //2.描述路徑 //一條路徑可以繪制多條線 路徑:path 路徑繪制多條線:path使用了兩次(2次的起點到終點),都是將線添加到某個點 UIBezierPath *path = [UIBezierPath bezierPath]; //設置起點 [path moveToPoint:CGPointMake(50, 150)]; //添加一根線Line到某個點 [path addLineToPoint:CGPointMake(250, 50)]; //畫第二根線 [path moveToPoint:CGPointMake(50, 250)]; [path addLineToPoint:CGPointMake(250, 100)]; //設置線寬 CGContextSetLineWidth(ctx, 20); //設置線的連接樣式 CGContextSetLineJoin(ctx, kCGLineJoinBevel); //設置線的頂角樣式 CGContextSetLineCap(ctx, kCGLineCapRound);// 圓角線條 //設置線條顏色 [[UIColor redColor] set]; //3.把路徑添加到上下文 CGContextAddPath(ctx, path.CGPath); //4.把上下文當中繪制的內容渲染到跟View關聯(lián)的layer CGContextStrokePath(ctx); }
運行效果:
6、畫曲線
本塞爾曲線原理:
- (void)drawRect:(CGRect)rect { //1.獲取跟View相關聯(lián)的上下文.】 CGContextRef ctx = UIGraphicsGetCurrentContext(); //2.描述路徑 UIBezierPath *path = [UIBezierPath bezierPath]; //畫曲線,設置起點.還有一個控制點(用來控制曲線的方向跟彎曲程度) //設置起點 [path moveToPoint:CGPointMake(10, 150)]; //添加一要曲線到某個點 [path addQuadCurveToPoint:CGPointMake(200, 150) controlPoint:CGPointMake(150, 10)]; //3.把路徑添加到上下文當中 CGContextAddPath(ctx, path.CGPath); //4.把上下文的內容渲染View上 CGContextStrokePath(ctx); }
運行效果:
7、畫餅圖
方法1:
- (void)drawRect:(CGRect)rect { NSArray *dataArray = @[@25,@25,@50]; // 畫弧 CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5); // 半徑 CGFloat radius = rect.size.width * 0.5 - 10; CGFloat startA = 0; CGFloat angle = 0; CGFloat endA = 0; for (NSNumber *num in dataArray) { startA = endA; // 遍歷出第一個對象25,angle =25/100 *2π,即angle = π/2,所以為1/4圓, angle = num.intValue / 100.0 * M_PI * 2; // 截至角度= 開始的角度+ 遍歷出的對象所占整個圓形的角度 endA = startA + angle; // 順勢針畫貝塞爾曲線 UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; // 設置隨機顏色 [[self randomColor] set]; // 添加一根線到圓心 [path addLineToPoint:center]; // 填充路徑 [path fill]; // 描邊路徑 // [path stroke]; } } /** 生成隨機顏色 @return UIColor */ -(UIColor *)randomColor{ CGFloat redLevel = rand() / (float) RAND_MAX; CGFloat greenLevel = rand() / (float) RAND_MAX; CGFloat blueLevel = rand() / (float) RAND_MAX; return [UIColor colorWithRed: redLevel green: greenLevel blue: blueLevel alpha: 1.0]; }
運行效果:
方法二:
- (void)drawRect:(CGRect)rect { CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * .5); CGFloat radius = self.bounds.size.width * 0.5 - 10; CGFloat startA = 0; CGFloat endA = 25 / 100.0 * M_PI * 2; UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; [[UIColor redColor] set]; //添加一根線到圓心 [path addLineToPoint:center]; [path fill]; //第二個扇形 startA = endA; CGFloat angle = 25 / 100.0 * M_PI * 2; endA = startA + angle; UIBezierPath *path2 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; [[UIColor greenColor] set]; //添加一根線到圓心 [path2 addLineToPoint:center]; [path2 fill]; startA = endA; angle = 50 / 100.0 * M_PI * 2; endA = startA + angle; UIBezierPath *path3 = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; [[UIColor blueColor] set]; //添加一根線到圓心 [path3 addLineToPoint:center]; [path3 fill]; } /** 生成隨機顏色 @return UIColor */ -(UIColor *)randomColor{ CGFloat redLevel = rand() / (float) RAND_MAX; CGFloat greenLevel = rand() / (float) RAND_MAX; CGFloat blueLevel = rand() / (float) RAND_MAX; return [UIColor colorWithRed: redLevel green: greenLevel blue: blueLevel alpha: 1.0]; }
注:
如果想實現點擊以下變換顏色可以加上如下代碼:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //重繪 [self setNeedsDisplay]; }
8、繪制文字
- (void)drawRect:(CGRect)rect { NSString *str = @"李峰峰博客:http://www.imlifengfeng.com/"; NSMutableDictionary *dict = [NSMutableDictionary dictionary]; //設置字體 dict[NSFontAttributeName] = [UIFont systemFontOfSize:30]; //設置顏色 dict[NSForegroundColorAttributeName] = [UIColor redColor]; //設置描邊 dict[NSStrokeColorAttributeName] = [UIColor blueColor]; dict[NSStrokeWidthAttributeName] = @3; //設置陰影 NSShadow *shadow = [[NSShadow alloc] init]; shadow.shadowColor = [UIColor greenColor]; shadow.shadowOffset = CGSizeMake(-2, -2); shadow.shadowBlurRadius = 3; dict[NSShadowAttributeName] = shadow; //設置文字的屬性 //drawAtPoint不會自動換行 //[str drawAtPoint:CGPointMake(0, 0) withAttributes:dict]; //drawInRect會自動換行 [str drawInRect:self.bounds withAttributes:dict]; }
運行效果:
9、加水印
// // ViewController.m // Quartz2DTest // // Created by 李峰峰 on 2017/2/6. // Copyright © 2017年 李峰峰. All rights reserved. // #import "ViewController.h" #import "MyView.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIImageView *myImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)]; [self.view addSubview:myImageView]; //生成一張圖片 //0.加載圖片 UIImage *oriImage = [UIImage imageNamed:@"test"]; //1.創(chuàng)建位圖上下文(size:開啟多大的上下文,就會生成多大的圖片) UIGraphicsBeginImageContext(oriImage.size); //2.把圖片繪制到上下文當中 [oriImage drawAtPoint:CGPointZero]; //3.繪制水印(雖說UILabel可以快速實現這種效果,但是我們也可以繪制出來) NSString *str = @"李峰峰博客"; NSMutableDictionary *dict = [NSMutableDictionary dictionary]; dict[NSFontAttributeName] = [UIFont systemFontOfSize:20]; dict[NSForegroundColorAttributeName] = [UIColor redColor]; [str drawAtPoint:CGPointZero withAttributes:dict]; //4.從上下文當中生成一張圖片 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); //5.關閉位圖上下文 UIGraphicsEndImageContext(); myImageView.image = newImage; } @end
運行效果:
10、屏幕截圖
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { //生成圖片 //1.開啟一個位圖上下文 UIGraphicsBeginImageContext(self.view.bounds.size); //2.把View的內容繪制到上下文當中 CGContextRef ctx = UIGraphicsGetCurrentContext(); //UIView內容想要繪制到上下文當中, 必須使用渲染的方式 [self.view.layer renderInContext:ctx]; //3.從上下文當中生成一張圖片 UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); //4.關閉上下文 UIGraphicsEndImageContext(); //把圖片轉成二進制流 //NSData *data = UIImageJPEGRepresentation(newImage, 1); NSData *data = UIImagePNGRepresentation(newImage); [data writeToFile:@"/Users/lifengfeng/Downloads/imlifengfeng.jpg" atomically:YES]; }
運行效果:
截圖而已,就跟普通截圖一樣,自己試。
總結
以上就是關于Quartz2D一些常用的案例,Quartz2D還可以實現更多效果,具體的根據需求去實現。希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
ios App加載本地HTML網頁,點擊網頁鏈接跳轉到app頁面的方法
下面小編就為大家分享一篇ios App加載本地HTML網頁,點擊網頁鏈接跳轉到app頁面的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01ios獲取數據之encodeURI和decodeURI的實例
下面小編就為大家?guī)硪黄猧os獲取數據之encodeURI和decodeURI的實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11