一、繪制基本圖形
1、Quartz 2D是一個二維繪圖引擎,同時支持iOS和Mac系統(Quartz 2D是封裝好一套繪圖的庫,里面放有很多繪圖框架提供使用)
Quartz 2D在ios開發中很重要的價值就是自定義view(自定義UI控件)
Quartz 2D能完成的工作:
1>繪制圖形 : 線條\三角形\矩形\圓\弧等
2>繪制文字
3>繪制\生成圖片(圖像)
4>讀取\生成PDF
5>截圖\裁剪圖片
6>自定義UI控件(最大的作用自定義view)
為了便于搭建美觀的UI界面,iOS提供了UIKit框架,里面有各種各樣的UI控件
UILabel:顯示文字
UIImageView:顯示圖片
UIButton:同時顯示圖片和文字(能點擊)
… …
利用UIKit框架提供的控件,拼拼湊湊,能搭建和現實一些簡單、常見的UI界面
但是,有些UI界面極其復雜、而且比較個性化,用普通的UI控件無法實現,這時可以利用Quartz2D技術將控件內部的結構畫出來,自定義控件的樣子
其實,iOS中大部分控件的內容都是通過Quartz2D畫出來的
因此,Quartz2D在iOS開發中很重要的一個價值是:自定義view(自定義UI控件)
Quartz 2D能做很多強大的事情,例如
裁剪圖片
涂鴉\畫板
手勢解鎖
2、圖形上下文(Graphics Context):是一個CGContextRef類型的數據
圖形上下文的作用
保存繪圖信息、繪圖狀態
圖形上下文類型不同決定繪制的輸出目標(繪制到什么地方去?)
(輸出目標可以是PDF文件、Bitmap或者顯示器的窗口上)
相同的一套繪圖序列,指定不同的Graphics Context,就可將相同的圖像繪制到不同的目標上
Quartz2D提供了以下幾種類型的Graphics Context:
Bitmap Graphics Context
PDF Graphics Context
Window Graphics Context
Layer Graphics Context
Printer Graphics Context
如何利用Quartz2D自定義view?(自定義UI控件)
如何利用Quartz2D繪制東西到view上?
首先,得有圖形上下文,因為它能保存繪圖信息,并且決定著繪制到什么地方去
其次,那個圖形上下文必須跟view相關聯,才能將內容繪制到view上面
以下實現的是Layer Graphics Context類型的:
自定義view的步驟:
1>新建一個類,繼承自UIView
2>實現- (void)drawRect:(CGRect)rect方法(此方法中默認自動生成一個與view相關聯的圖形上下文)
3>在這個方法中取得跟當前view相關聯的圖形上下文
4>繪制相應的圖形內容
5>利用圖形上下文將繪制的所有內容渲染顯示到view上面
為什么要實現drawRect:方法才能繪圖到view上?
因為在drawRect:方法中才能取得跟view相關聯的圖形上下文
drawRect:方法在什么時候被調用?
當view第一次顯示到屏幕上時調用(被加到UIWindow上顯示出來)
調用view的setNeedsDisplay或者setNeedsDisplayInRect:時
Quartz2D的API是純C語言的,Quartz2D的API來自于Core Graphics框架
數據類型和函數基本都以CG作為前綴
CGContextRef
CGPathRef
CGContextStrokePath(ctx);
在drawRect:方法中取得上下文后,就可以繪制東西到view上
具體實現步驟:
1>自定義View
2>在view的根類中實現- (void)drawRect:(CGRect)rect 方法
3>在方法中=>
- (void)drawRect:(CGRect)rect
{
(1)畫線
//獲取圖形上下文(獲取,創建上下文,都以UIGraphics開頭)
CGContextRef context = UIGraphicsGetCurrentContext();
//繪制路徑
UIBezierPath *path = [UIBezierPath bezierPath];
//設置起點
[path moveToPoint:CGPointMake(50, 50)];
//添加一根線到終點
[path addLineToPoint:CGPointMake(100, 100)];
//設置線的顏色
[[UIColor redColor] set];
//設置線的寬度
CGContextSetLineWidth(context, 20);
//把繪制的內容添加到上下文當中
CGContextAddPath(context, path.CGPath);
//把上下文的內容顯示到View上(渲染到View的layer)
CGContextStrokePath(context);
//CGContextFillPath(context);
(2)曲線
CGContextRef ctx = UIGraphicsGetCurrentContext();
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 100)];
//controlPoint控制點
[path addQuadCurveToPoint:CGPointMake(100, 100) controlPoint:CGPointMake(50, 30)];
CGContextAddPath(ctx, path.CGPath);
CGContextStrokePath(ctx);
(3)畫三角形
CGContextRef context = UIGraphicsGetCurrentContext();
//繪制路徑
UIBezierPath *path = [UIBezierPath bezierPath];
//設置起點
[path moveToPoint:CGPointMake(50, 50)];
//添加一根線到終點,并且以終點作為起點再畫一條線
[path addLineToPoint:CGPointMake(100, 100)];
[path addLineToPoint:CGPointMake(50, 200)];
//設置線的顏色
[[UIColor redColor] set];
//線的冒冒設置為圓角
CGContextSetLineCap(context, kCGLineCapRound);
//設置線的寬度
CGContextSetLineWidth(context, 5);
//把繪制的內容添加到上下文當中
CGContextAddPath(context, path.CGPath);
//終點與起點閉合
CGContextClosePath(context);
//把上下文的內容顯示到View上(渲染到View的layer)
CGContextStrokePath(context);
//CGContextFillPath(context);(Fill有填充及自動關閉路徑的功能)
(4)畫矩形
1>//獲取圖形上下文(獲取,創建上下文,都以UIGraphics開頭)
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 50, 100)];(寬高一樣就是正方形)
2>只寫下面方法能快速填充出一個矩形
[[UIColor redColor]set];
UIRectFill(CGRectMake(50, 50, 50, 50));
(5)圓角矩形(如果當前的矩形是正方形設置圓角半徑等于寬畫成圓了,如果不是正方形,設置圓角半徑等于寬畫成橢圓)
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) cornerRadius:20];
[[UIColor redColor]set];
CGContextAddPath(ctx, path.CGPath);
//CGContextStrokePath(ctx);
CGContextFillPath(ctx);(填充)
(6)畫橢圓(寬高一樣,畫成圓)
//注意:以后使用以下的寫法,可以省列很多步驟
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 100, 50)];
[path stroke];//(該方法中封裝了以下步驟:1、獲取上下文 2、描述路徑 3、把路徑添加到上下文 4、把上下文的內容渲染到view上)
[path fill];(填充)//跟以上同有封裝功能
(7)畫弧
//參數Center:弧所在的圓心
//參數radius:圓的半徑
//參數startAngle:開始的角度
//參數endAngle:截止角度
//參數clockwise:YES順時針 NO:逆時針
//注意:不能直接調用父類的self.center,是因為self.center坐標是相對于它的父控件,不能使用,必須要使用自定義view的坐標,drawRect方法提供的參數rect就是自定義view的范圍
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:M_PI_2 endAngle:-M_PI_2 clockwise:YES];
[path stroke];
(8)扇形
//參數Center:弧所在的圓心
//參數radius:圓的半徑
//參數startAngle:開始的角度
//參數endAngle:截止角度
//參數clockwise:YES順時針 NO:逆時針
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:- M_PI endAngle:-M_PI_2 clockwise:YES];
[path addLineToPoint:center];
[[UIColor redColor]set];
// [path closePath];
// [path stroke];
[path fill];
(9)畫文字
NSString *str = @"我愛您sdgsdgsdgfdbfdsfagsdgsg";
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
dictM[NSFontAttributeName] = [UIFont systemFontOfSize:50 weight:20];
dictM[NSForegroundColorAttributeName] = [UIColor redColor];
//描邊
dictM[NSStrokeWidthAttributeName] = @3;
dictM[NSStrokeColorAttributeName] = [UIColor blueColor];
//設置陰影
NSShadow *shadow = [[NSShadow alloc]init];
shadow.shadowColor = [UIColor orangeColor];
shadow.shadowOffset = CGSizeMake(1, 2);
dictM[NSShadowAttributeName] = shadow;
//[str drawAtPoint:CGPointMake(0, 0) withAttributes:dictM];
[str drawInRect:rect withAttributes:dictM];//此方法會自動換行
(10)畫圖片
UIImage *image = [UIImage imageNamed:@"people_wangxinling"];
// UIRectClip(CGRectMake(0, 0, 50, 50));(裁剪,必須在繪制之前進行設置)
// [image drawInRect:rect];(把藥繪制的圖片給填充給定的區域當中)
// [image drawAtPoint:CGPointMake(0, 0)];//(繪制的是原始圖片的大小)
[image drawAsPatternInRect:rect];(平鋪)
(11)餅圖(在自定義view的跟類中實現)
- (void)drawRect:(CGRect)rect {
NSArray *arr = @[@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 arr) {
startA = endA;
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 radamColor]set];
[path addLineToPoint:center];
[path fill];
}
}
- (UIColor *)radamColor
{
CGFloat r = arc4random_uniform(256)/255.0;
CGFloat g = 0;
CGFloat b = 0;
while ( r == g) {
g = arc4random_uniform(256)/255.0;
}
while ( r == g == b) {
b = arc4random_uniform(256)/255.0;
}
return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self setNeedsDisplay];
}
3、下載進度條例子實現
//%在stringWithFormat有特殊含義,不能直接使用,如果想要使用用兩個%代表一個%
self.LabelSlider.text = [NSString stringWithFormat:@"%.2f%%", sender.value];
(2)注意:- (void)drawRect:方法如果是手動調用的話,它是不會給你創建view相關聯的上下文,只有系統調用該方法時,才會創建跟view相關聯的上下文,所以一般使用以下方法,讓系統自動調用- (void)drawRect:方法
[self setNeedsDisplay];
4、新的定時器(不使用NSTimer,在自定義view的跟類中實現)
- (void)awakeFromNib
{
[super awakeFromNib];
//舊的定時器
//[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(changeY) userInfo:nil repeats:YES];
//新的定時器
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeY)];
//想要讓CADisplayLink讓它工作,必須要把他添加到主運行循環當中
//當每一次屏幕刷新的時候就會調用指定的方法(屏幕每一秒刷新60次)
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
CGFloat cc = 0;
- (void)changeY
{
cc += 10;
if (cc > self.bounds.size.height) {
cc = 0;
}
//setNeedsDisplay會調用drawRect:方法,但是它并不是立馬調用,只是設了一個表示,當下一次屏幕刷新的時候才會調用drawRect:方法,所以與上面新的定時器同步跑時看起來比較順滑
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed:@"people_wangxinling"];
[image drawInRect:CGRectMake(0, cc, 50, 50)];
}
5、圖形上下文棧(面試題)
(1)//一執行下面的方法,就會獲取跟view相關聯的上下文,在內存中分配存儲空間
//上下文在內存中的結構分為上下兩個區域:1>上面區域是保存路徑,2>下面區域是保存上下文的狀態(默認此區域的線寬為1,顏色為黑色)
CGContextRef ctx = UIGraphicsGetCurrentContext();
(2)//描述路徑
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(20, 20)];
[path addLineToPoint:CGPointMake(60, 60)];
//以下代碼對上下文狀態區域的狀態進行修改
CGContextSetLineWidth(ctx, 10);
[UIColor redColor] set];
(3)//把路徑添加到上下文中(當執行完這行代碼,就把線添加到上下文保存路徑的區域)
CGContextAddPath(ctx, path.CGPath);
//
(4)//把上下文中的內容渲染到view當中
當執行次操作:會到上下文內存的保存路徑的區域取出全部內容,然后把保存在上下文狀態區域里的狀態設置給取出的內容
CGContextStrokePath(ctx);
CGContextFillPath(ctx);
補一://保存當前上下文的狀態(當執行此代碼,就把上下文狀態區域里的狀態復制一份保存到狀態棧中)
CGContextSaveGState(ctx);
補二://從上下文狀態棧當中恢復上下文的狀態(執行此代碼,取出上下文狀態棧中最上面的狀態來進行設值,并且恢復上下文狀態區域的狀態為上下文狀態棧中最上面的狀態)
CGContextRestoreGState(ctx);
6、圖形上下文的矩陣操作
CGContextTranslateCTM(ctx, 10, 10);
//旋轉(最常用)
CGContextRotateCTM(ctx, M_PI);
//縮放
CGContextScaleCTM(ctx, 1.5, 1.5);
7、圖片加水印(是生成一張新的圖片,在任何地方都可實現,不是在view上畫東西,所以不需要在 - (void)drawRect:(CGRect)rect實現)
位圖上下文需要手動去開啟,開啟多大的上下文,生成的圖片就多大
//0、加載圖片
UIImage *image = [UIImage imageNamed:@"people_caoying"];
//1、開啟一個跟圖片原始大小的上下文
//參數:opaque不透明
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//2.把圖片繪制到上下文當中
[image drawAtPoint:CGPointZero];
//3.把文字繪制到上下文當中
NSString *str = @"x阿貝";
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
dictM[NSFontAttributeName] = [UIFont systemFontOfSize:100 weight:20];
dictM[NSForegroundColorAttributeName] = [UIColor redColor];
[str drawAtPoint:CGPointMake(10,20) withAttributes:dictM];
//4.從上下文當中生成一張新的圖片
UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext();
//5.關閉上下文
UIGraphicsEndPDFContext();
self.imageV.image = newimage;
Mode參數決定繪制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)
繪制空心路徑
void CGContextStrokePath(CGContextRef c)
繪制實心路徑
void CGContextFillPath(CGContextRef c)
提示:一般以CGContextDraw、CGContextStroke、CGContextFill開頭的函數,都是用來繪制路徑的
將當前的上下文copy一份,保存到棧頂(那個棧叫做”圖形上下文?!?
void CGContextSaveGState(CGContextRef c)
將棧頂的上下文出棧,替換掉當前的上下文
void CGContextRestoreGState(CGContextRef c)