iOS繪圖框架CoreGraphics分析

来源:http://www.cnblogs.com/fishbay/archive/2017/08/08/7308023.html
-Advertisement-
Play Games

由於`CoreGraphics`框架有太多的`API`,對於初次接觸或者對該框架不是十分瞭解的人,在繪圖時,對`API`的選擇會感到有些迷茫,甚至會覺得`iOS`的圖形繪製有些繁瑣。因此,本文主要介紹一下`iOS`的繪圖方法和分析一下`CoreGraphics`框架的繪圖原理。 ...


由於CoreGraphics框架有太多的API,對於初次接觸或者對該框架不是十分瞭解的人,在繪圖時,對API的選擇會感到有些迷茫,甚至會覺得iOS的圖形繪製有些繁瑣。因此,本文主要介紹一下iOS的繪圖方法和分析一下CoreGraphics框架的繪圖原理。

一、繪圖系統簡介

iOS的繪圖框架有多種,我們平常最常用的就是UIKit,其底層是依賴CoreGraphics實現的,而且絕大多數的圖形界面也都是由UIKit完成,並且UIImageNSStringUIBezierPathUIColor等都知道如何繪製自己,也提供了一些方法來滿足我們常用的繪圖需求。除了UIKit,還有CoreGraphicsCore AnimationCore ImageOpenGL ES等多種框架,來滿足不同的繪圖要求。各個框架的大概介紹如下:

  • UIKit:最常用的視圖框架,封裝度最高,都是OC對象
  • CoreGraphics:主要繪圖系統,常用於繪製自定義視圖,純C的API,使用Quartz2D做引擎
  • CoreAnimation:提供強大的2D和3D動畫效果
  • CoreImage:給圖片提供各種濾鏡處理,比如高斯模糊、銳化等
  • OpenGL-ES:主要用於游戲繪製,但它是一套編程規範,具體由設備製造商實現

繪圖系統

圖1

二、繪圖方式

實際的繪圖包括兩部分:視圖繪製視圖佈局,它們實現的功能是不同的,在理解這兩個概念之前,需要瞭解一下什麼是繪圖周期,因為都是在繪圖周期中進行繪製的。

繪圖周期

  • iOS在運行迴圈中會整合所有的繪圖請求,並一次將它們繪製出來
  • 不能在子線程中繪製,也不能進行複雜的操作,否則會造成主線程卡頓

1.視圖繪製

調用UIViewdrawRect:方法進行繪製。如果調用一個視圖的setNeedsDisplay方法,那麼該視圖就被標記為重新繪製,並且會在下一次繪製周期中重新繪製,自動調用drawRect:方法。

2.視圖佈局

調用UIViewlayoutSubviews方法。如果調用一個視圖的setNeedsLayout方法,那麼該視圖就被標記為需要重新佈局,UIKit會自動調用layoutSubviews方法及其子視圖的layoutSubviews方法。

在繪圖時,我們應該儘量多使用佈局,少使用繪製,是因為佈局使用的是GPU,而繪製使用的是CPUGPU對於圖形處理有優勢,而CPU要處理的事情較多,且不擅長處理圖形,所以儘量使用GPU來處理圖形。

三、繪圖狀態切換

iOS的繪圖有多種對應的狀態切換,比如:pop/pushsave/restorecontext/imageContextCGPathRef/UIBezierPath等,下麵分別進行介紹:

1.pop / push

設置繪圖的上下文環境(context)

push:UIGraphicsPushContext(context)把context壓入棧中,並把context設置為當前繪圖上下文

pop:UIGraphicsPopContext將棧頂的上下文彈出,恢復先前的上下文,但是繪圖狀態不變

下麵繪製的視圖是黑色

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setFill];
    UIGraphicsPushContext(UIGraphicsGetCurrentContext());
    [[UIColor blackColor] setFill];
    UIGraphicsPopContext();
    UIRectFill(CGRectMake(90, 340, 100, 100)); // black color
}

2.save / restore

設置繪圖的狀態(state)

save:CGContextSaveGState 壓棧當前的繪圖狀態,僅僅是繪圖狀態,不是繪圖上下文

restore:恢復剛纔保存的繪圖狀態

下麵繪製的視圖是紅色

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setFill];
    CGContextSaveGState(UIGraphicsGetCurrentContext());
    [[UIColor blackColor] setFill];
    CGContextRestoreGState(UIGraphicsGetCurrentContext());
    UIRectFill(CGRectMake(90, 200, 100, 100)); // red color
}

3.context / imageContext

iOS的繪圖必須在一個上下文中繪製,所以在繪圖之前要獲取一個上下文。如果是繪製圖片,就需要獲取一個圖片的上下文;如果是繪製其它視圖,就需要一個非圖片上下文。對於上下文的理解,可以認為就是一張畫布,然後在上面進行繪圖操作。

context:圖形上下文,可以通過UIGraphicsGetCurrentContext:獲取當前視圖的上下文

imageContext:圖片上下文,可以通過UIGraphicsBeginImageContextWithOptions:獲取一個圖片上下文,然後繪製完成後,調用UIGraphicsGetImageFromCurrentImageContext獲取繪製的圖片,最後要記得關閉圖片上下文UIGraphicsEndImageContext

4.CGPathRef / UIBezierPath

圖形的繪製需要繪製一個路徑,然後再把路徑渲染出來,而CGPathRef就是CoreGraphics框架中的路徑繪製類,UIBezierPath是封裝CGPathRef的面向OC的類,使用更加方便,但是一些高級特性還是不及CGPathRef

四、具體繪圖方法

由於iOS常用的繪圖框架有UIKitCoreGraphics兩個,所以繪圖的方法也有多種,下麵介紹一下iOS的幾種常用的繪圖方法。

1.圖片類型的上下文

圖片上下文的繪製不需要在drawRect:方法中進行,在一個普通的OC方法中就可以繪製

使用UIKit實現

// 獲取圖片上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
// 繪圖
UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
// 從圖片上下文中獲取繪製的圖片
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
// 關閉圖片上下文
UIGraphicsEndImageContext();

使用CoreGraphics實現

// 獲取圖片上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
// 繪圖
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);
// 從圖片上下文中獲取繪製的圖片
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
// 關閉圖片上下文
UIGraphicsEndImageContext();

2.drawRect:

UIView子類的drawRect:方法中實現圖形重新繪製,繪圖步驟如下:

  • 獲取上下文
  • 繪製圖形
  • 渲染圖形

UIKit方法

- (void) drawRect: (CGRect) rect {
    UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
    [[UIColor blueColor] setFill];
    [p fill];
}

CoreGraphics

- (void) drawRect: (CGRect) rect {
    CGContextRef con = UIGraphicsGetCurrentContext();
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
    CGContextFillPath(con);
}

3.drawLayer:inContext:

UIView子類的drawLayer:inContext:方法中也可以實現繪圖任務,它是一個圖層的代理方法,而為了能夠調用該方法,需要給圖層的delegate設置代理對象,其中代理對象不能是UIView對象,因為UIView對象已經是它內部根層(隱式層)的代理對象,再將它設置為另一個層的代理對象就會出問題。

一個view被添加到其它view上時,圖層的變化如下:

  • 先隱式地把此viewlayerCALayerDelegate設置成此view
  • 調用此viewself.layerdrawInContext方法
  • 由於drawLayer方法的註釋:If defined, called by the default implementation of -drawInContext:說明瞭drawInContextif([self.delegate responseToSelector:@selector(drawLayer:inContext:)])就執行drawLayer:inContext:方法,這裡我們因為實現了drawLayer:inContext:所以會執行
  • [super drawLayer:layer inContext:ctx]會讓系統自動調用此viewdrawRect:方法,至此self.layer畫出來了
  • self.layer上再加一個子layer,當調用[layer setNeedsDisplay];時會自動調用此layerdrawInContext方法
  • 如果drawRect不重寫,就不會調用其layerdrawInContext方法,也就不會調用drawLayer:inContext方法

調用內部根層的drawLayer:inContext:

//如果drawRect不重寫,就不會調用其layer的drawInContext方法,也就不會調用drawLayer:inContext方法
-(void)drawRect:(CGRect)rect{
    NSLog(@"2-drawRect:");
    NSLog(@"drawRect里的CGContext:%@",UIGraphicsGetCurrentContext());
    //得到的當前圖形上下文正是drawLayer中傳遞過來的
    [super drawRect:rect];
}

#pragma mark - CALayerDelegate
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    
    NSLog(@"1-drawLayer:inContext:");
    NSLog(@"drawLayer里的CGContext:%@",ctx);
    // 如果去掉此句就不會執行drawRect!!!!!!!!
   [super drawLayer:layer inContext:ctx];
}

調用外部代理對象的drawLayer:inContext:

由於不能把UIView對象設置為CALayerDelegate的代理,所以我們需要創建一個NSObject對象,然後實現drawLayer:inContext:方法,這樣就可以在代理對象里繪製所需圖形。另外,在設置代理時,不需要遵守CALayerDelegate的代理協議,即這個方法是NSObject的,不需要顯式地指定協議。

// MyLayerDelegate.m
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGContextAddEllipseInRect(ctx, CGRectMake(100,100,100,100));
    CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
    CGContextFillPath(ctx);
}

// ViewController.m

@interface ViewController () <CALayerDelegate>

@property (nonatomic, strong) id myLayerDelegate;

@end

@implementation ViewController

- (void)viewDidLoad {
    // 設置layer的delegate為NSObject子類對象
    _myLayerDelegate = [[MyLayerDelegate alloc] init];
    
    MyView *myView = [[MyView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:myView];
    CALayer *layer = [CALayer layer];
    layer.backgroundColor = [UIColor magentaColor].CGColor;
    layer.bounds = CGRectMake(0, 0, 300, 500);
    layer.anchorPoint = CGPointZero;
    layer.delegate = _myLayerDelegate;
    [layer setNeedsDisplay];
    [myView.layer addSublayer:layer];
}

詳細實現過程

UIView需要顯示時,它內部的層會準備好一個CGContextRef(圖形上下文),然後調用delegate(這裡就是UIView)的drawLayer:inContext:方法,並且傳入已經準備好的CGContextRef對象。而UIViewdrawLayer:inContext:方法中又會調用自己的drawRect:方法。平時在drawRect:中通過UIGraphicsGetCurrentContext()獲取的就是由層傳入的CGContextRef對象,在drawRect:中完成的所有繪圖都會填入層的CGContextRef中,然後被拷貝至屏幕。


iOS繪圖框架分析如上,如有不足之處,歡迎指出,共同進步。(本文圖片來自互聯網,版權歸原作者所有)

參考資料

iOS繪圖系統(一) UIKit與CoreGraphics

CoreGraphics之CGContextSaveGState與UIGraphicsPushContext

Core Graphics快速入門——從一行代碼說起

iOS繪圖教程

UIGraphicsPushContext

Basic Zooming Using the Pinch Gestures

iOS開發UI篇—CAlayer(自定義layer)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.封裝$函數 2.取色器 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6 <script src="js/select.js"></script> ...
  • 新建項目 開始(確認已經安裝node環境和npm包管理工具) 1、新建項目文件名為start_vuedemo 2、npm init -y 初始化項目,我的win7系統,工程在d盤的vue_test_project文件夾下的名為start_vuedemo的工程文件夾 如圖所示: 在該工程下自動生成一個 ...
  • 響應式網格系統 ======================================== 響應式網格系統的概念 把網頁劃分成列、行、外邊距、隔離帶(各列與各行之間的空白)等由一系列相交的水平軸和垂直軸組成的網格。 網格佈局的作用在於更有效地控制元素在網頁中所占比例的大小。比如,博客中有一個留 ...
  • 上一篇《Vue開發環境搭建及熱更新》,我們講解了vue開發環境的搭建還有一些小問題,接下來我們來講解一下這個界面是如何形成的。 在開始講之前,我們先來看看我們上一篇所謂的項目目錄裡面到底放著什麼。 1.index.html文件入口 ; 2.src放置組件和入口文件 ; 3.node_modules為 ...
  • 1、color 解析顏色 把代表顏色的字元串轉換為顏色值 2、data-uri 把我們需要使用的素材圖片轉化成BASE64編碼 ,項目中尤其是移動端的項目,節省了圖片的載入時間,是圖片優化的一個手段;LESS對於大圖片是不能轉碼的,我們可以使用BASE64工具轉碼(tool.css-js.com) ...
  • 1.使用cdn引入font_awesome圖標庫的css文件 例如:index.htm <html><head><title>font_awesome test</title><link rel="stylesheet" href="https://cdn.bootcss.com/font-awes ...
  • 這是模塊化開發、主流框架和最新版的ECMAScript語法規範的一個小demo 準備工作 安裝 nodeJs 首先進入node官網,去下載最新版的nodeJs webpack 安裝webpack npm install webpack -g 參數-g表示全局安裝webpack,你在cmd命令中哪個文 ...
  • 當變數為'',false,null,undefined,0,NaN時,返回預設值 "hello world" 0 || 'ccc' "ccc" NaN || 'ccc' "ccc" 我感覺想不通一個問題 '' == false ==>true false == false ==>true null= ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...