Quartz 2D (1)

一、繪制基本圖形

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)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容