iOS UI繪製原理

高質量的圖形展現在app的交互界面中扮演很是重要的角色。高質量的圖形展現讓用戶更能喜歡使用它。iOS系統主要提供兩種途徑去建立高質量的圖形:OpenGL或者使用原生Quarts、Core Animation和UIKit。本文會展開講一下後者。html

Quartz是主要的繪製途徑,它提供了基於路徑繪製、抗鋸齒繪製、漸變色、圖形繪製、顏色、變形和PDF文檔的建立展現和解析能力。UIKit是對Quartz的線條、圖片和顏色操做的封裝。Core Animation提供了對在動畫中修改UIView屬性的的支持,同時還能夠實現自定義動畫。安全

這個章節會講述iOS App中的渲染過程,並說明其中用到的繪製原理。能夠幫你學習到一些可讓你本身app優化渲染的小技巧。app

重要:並不是全部UIKit的類都不是線程安全的。請確認在執行繪製的時候處於主線程。框架

UIKit 圖形系統

在iOS中,不管使用哪一種技術(OpenGL、Quartz、UIKit或者Core Animation)繪製在UIView或者子類中,都會在屏幕上展現。視圖定義本身在屏幕上如何繪製,或者說如何展示本身。系統提供的視圖會自動定義本身的展示。自定義視圖你必須定義視圖如何展示。本章就會講解經過Quartz、Core Animation和UIKit去繪製自定義圖形。ide

另外,能夠離屏繪製位圖和PDF圖形上下文(譯者吐槽:是否是意味着這部分能夠不在主線程繪製?)。當你離屏繪製時,由於沒有在視圖上進行繪製,因此視圖的生命週期也對此不起做用。函數

視圖生命週期

UIView的子類包含了最基本的圖形繪製模型 -- 根據須要更新繪製自身。UIView類經過批量更新繪製以及在合適的時機更新繪製自身來作到讓更新自身繪製變得簡單和高效。性能

當一個視圖第一次或者某部分須要更新的時候iOS系統老是會去請求drawRect:方法。學習

如下是觸發視圖更新的一些操做:字體

  • 移動或刪除視圖
  • 經過將視圖的hidden屬性設置爲NO
  • 滾動消失的視圖再次須要出如今屏幕上
  • 視圖顯式調用setNeedsDisplaysetNeedsDisplayInRect:方法

視圖系統都會自動觸發從新繪製。對於自定義視圖,就必須重寫drawRect:方法去執行全部繪製。在方法中經過原生繪製API來繪製自己的形狀、文字、圖片、漸變或者任何你但願展現的部分。視圖第一次展現的時候,iOS系統會傳遞正方形區域來表示這個視圖繪製的區域,爲了最大程度優化性能,重繪的時候最好只重繪受影響的部分優化

在調用drawRect:方法以後,視圖就會把本身標記爲已更新,而後等待下一次視圖更新被觸發。靜態自定義視圖須要處理由於滾動而出現或者由於其餘視圖出現而引發的視圖變化。(譯者吐槽:後半句啥意思?沒太懂)

若是想要改變視圖的內容,就必須觸發視圖重繪內容。經過調用setNeedsDisplay或者setNeedsDisplayInRect:方法來觸發更新。使用場景例如一秒內屢次更新視圖,或者根據用戶的交互行爲在視圖中出現新的內容。

重要:不要顯式調用drawRect:方法。這個方法應該只留給iOS須要從新繪製的時候留給系統調用。由於在其餘時機圖形上下文是不存在的,因此也不能對屏幕進行繪製。(圖形上下文下一個小節會說明。)

座標系統及iOS中的繪製

當App須要在iOS系統中繪製圖形時,它必須繪製在一個二維座標系中。這看上去很簡單,但在某些繪製狀況下須要處理另外一種不一樣的座標系。

iOS的圖形繪製須要依靠圖形上下文來完成。理論上說,圖形上下文是用來描述在哪裏、如何畫上,包括顏色繪製、切割繪製區域、線條粗細和樣式等信息。

另外,圖1-1中展現了每一個圖形上下文都有一個座標系。更準確的說,每一個圖形上下都三個座標系:

  • 繪製座標系。繪圖上下文經過使用指令繪製的座標。
  • 視圖座標系。相對於視圖的固定座標系。
  • 設備座標系。物理屏幕的像素展現座標。

圖1-1 繪製座標、視圖座標以及硬件座標關係

圖1-1

譯者注:CTM是Quartz中的一個概念,下面會介紹。

iOS的繪圖框架爲繪製特定的目標(屏幕、位圖、PDF內容等)建立圖形上下文,這些圖形上下文爲該目的地創建初始繪圖座標系。這個初始的座標系被稱爲默認座標系,是1比1映射到視圖座標系上的。(譯者吐槽:後半句沒懂)

每一個視圖都有一個本身的current transformation matrix(CTM),一個數字舉證映射當前繪製座標系到視圖座標系。App能夠修改矩陣來影響後面發生的繪製操做。

iOS會在默認座標系的基礎上建立圖形上下文。在iOS中主要是兩種:

  • 左上原點座標系(ULO),從左上角爲0,0座標,向右向下爲正,UIKit和Core Animation都是基於ULO。
  • 左下原點座標系(LLO),從左下角爲0,0座標,向右向上爲正,Core Graphics是基於LLO。

兩種座標系展現如圖1-2

圖1-2iOS中默認座標系

圖1-2

提示:MacOS默認使用的是LLO。經過AppKit和CoreGraphics繪製都是基於此座標系,AppKit提供了左上原點座標系的轉換支持。

點和像素

iOS系統中指定的座標系和底層設備繪製像素的之間有區別。當使用原生繪製列如Quartz,UIKit和Core Animation,繪製座標西和試圖座標系都是邏輯座標系(譯者注:這裏說的邏輯座標系也就是指的不與設備像素點對應),座標系數值表示的是,與設備上的像素並無一一對應的關係。

系統會自動根據視圖的點座標值去映射到設備的像素上,但並不必定是一對一映射,這點很是重要。

一個點不必定映射到物理的一個像素。

使用點代替的像素的主要目的仍是爲了讓視圖在設備上呈現出合適的尺寸,不會由於屏幕像素變高致使本來視圖變得很小。具體多少像素對應一個點,是由系統根據當前設備硬件來決定的。例如,在視網膜屏幕上,一條線的繪製對應像個像素的線條寬度。這種映射關係讓普通屏視網膜屏和更高分辨率的屏上展現視圖的大小基本保持一致。

提示:Core Graphics中渲染和打印PDF的時候一個點點對應1/72英寸。

在iOS中,UIScreen,UIView,UIImage和CALayer都提供屬性用於描述像素和點之間的映射比例。例如,UIKit的View的contentScaleFactor屬性。在非視網膜屏中,該屬性值爲1.0。在視網膜屏中,爲2.0(譯者注:除了plus系列後應該還有3.0)。在將來也可能出現其餘的值。(在iOS4以前一直都是1.0)。

由於自動映射的關係,在繪製視圖時通暢不須要關心像素。只有在下載高分辨率圖片在視網膜屏幕上展現的時候,須要關心圖片渲染的scale,避免高分圖被低分渲染而變大的問題。

在iOS中,當你在屏幕上繪製東西時,圖形子系統使用一種叫作反鋸齒的技術,在低分辨率的屏幕上近似一個高分辨率的圖像。用一個例子來解釋下。繪製一條黑色的豎線在白色背景上,若是線正好落在像素上,展示出來就以下圖左邊那樣是一系列黑色像素排列。若是正好落在兩個像素上,那就會出現灰色像素繪製兩格以下圖1-3右側。

圖1-3

整數值的點座標會落在兩個像素的中間。例如,畫一條1個像素寬度的直線(1,1)到(1,10),獲得的是一條灰色的先。若是畫兩個像素寬度的線,纔會獲得一條黑色的實線,由於兩個像素正好落滿兩個像素。通常來講,若是不調整它們的位置,使它們徹底覆蓋像素,那麼與寬度爲偶數的物理像素的寬度相比,奇數個物理像素寬的線顯得更淺。

scale屬性就是爲了表示一個點映射了多少像素。

在非視網膜屏上scale永遠爲1.0,一個點對應一個像素。爲了不反鋸齒,當你繪製一個單點線時,若是佔了奇數整數寬度,那就須要偏移0.5個點,若是佔用偶數寬度則沒必要這麼作。

圖1-4 一個點寬度的線在非視網膜和視網膜屏上的展現

在視網膜的scale爲2.0,一點線也不會觸發抗鋸齒,由於自己就會撐滿兩個像素。若是要畫一條一個像素的線,就須要使用0.5個點的寬度而且偏移0.25個點。

直接按照scale去控制像素繪製並不能獲得最好的體驗。一個一像素寬的線在非視網膜屏幕上看起來可能沒問題,但若是在視網膜屏幕上看起來就會以爲太細了。這取決於你如何去繪製。

獲取圖像上下文

圖像上下文能夠在drawRect:方法中獲取到,而且馬上進行繪製。UIView爲圖像上下文提供了繪製的環境。

若是您想在視圖之外的地方繪製(例如,在一個PDF或位圖文件中捕獲一系列繪圖操做),或者若是您須要調用須要上下文對象的核心圖形函數,那麼您必須採起額外的步驟來獲取圖形上下文對象。下面的章節解釋了爲何。

更多關於修改圖像上下文狀態和建立定製內容請參考 [ Quartz 2D Programming Guide ]。圖像上下文的方法清單請參考 [ CGContext Reference ], [ CGBitmapContext Reference ], [ CGPDFContext Reference ]

在屏幕上繪製

想要在屏幕上繪製,就須要在drawRect:方法中獲取到圖像上下文。(這一系列方法中的第一個參數都是一個CGContextRef對象。)能夠經過調用UIGraphicsGetCurrtnContext方法,在drawRect:方法中獲取一個圖形上下文。(屢次獲取也會獲得同一個。)

在UIKit的view中,使用Core Graphics系列方法來繪製是基於ULO座標系的。或者,翻轉CTM來使用LLO座標系來繪製。詳細的請閱讀 [ Flipping the Default Coordinate System ]

UIGraphicsGetCurrentContext函數始終返回的是當前的上下文。例如,在建立PDF後獲取的上下文就是PDF上下文。只要是使用Core Graphics系列函數繪製,都必須使用這個方法來獲取上下文。

提示: 打印相關的函數放在了UIPrintPageRender類中。相似於drawRect:,UIKit在其中提供了打印相關的實現。而且默認也是基於ULO座標系。

繪製位圖和PDF

UIKit提供了繪製位圖和PDF的上下文和系列函數。兩種建立方式都須要分別調用一個函數來建立其對應的上下文。在經過上下文進行繪製,並在繪製完成後關閉上下文。

兩種上下文也是基於ULO座標系。Core Graphics提供了一系列方法用於在在位圖上下文中徐然和在PDF上下文中繪製。上下文從Core Graphics中直接調用函數得到,而且基於LLO座標系繪製。

**提示:**在iOS中仍是推薦使用UIKit的相關函數來獲取上下文來繪製。若是非要使用Core Graphics中的相關方法來繪製,則須要對座標系的差別作兼容。(譯者:因此UIKit中的上下文相關方法實際上是轉換了座標系的Core Graphics方法。)

詳細能夠參考 建立繪製位圖建立PDF

顏色和色域

儘管Quartz在iOS系統中支持全色域;可是幾乎全部app中都只用到了RGB色域。畢竟iOS被設計在屏幕上繪製渲染,而RGB是最合適的。

UIColor對象經過提供一系列便捷方法經過RGB/HSB和灰度色值來建立顏色,且不須要關心色域問題,而由UIColor對象自動決定。

也可使用Core Graphics框架中的CGContextSetRGBStrokeColorCGContextSetRGBFillColor函數來建立社設置顏色。儘管Core Graphics提供了能夠指定色域和建立自定義色域的函數,可是並不推薦在代碼中使用。(譯者:爲啥不推薦?緣由呢?)仍是推薦始終使用RGB色域便可。

使用Quartz和UIKit繪製

咱們把iOS中的繪圖技術統稱爲Quartz。而Core Graphics框架則是Quartz心臟,並承擔大多數繪製內容的職責。框架提供了數據類型和函數支持如下能力:

  • 圖形上下文
  • 路徑
  • 圖片和位圖
  • 透明圖層
  • 顏色和色域
  • 漸變和陰影
  • 字體
  • PDF

UIKit在Quartz基礎上提供了一套圖形操做相關的類。目的並非爲了替代Core Graphics,相反,他們是爲了給UIKit的其餘類提供繪畫支持:

  • UIImage/UIColor/UIFont/UIScreen/UIBezierPath
  • 生成一個JPEG或PNG的圖片對象的函數
  • 獲取位圖上下文的函數
  • 獲取PDF上下文的函數
  • 繪製矩形和裁剪繪圖區域的函數
  • 獲取當前圖形上下文的函數

更多信息參考,UIKit Framework Reference,還有 Core Graphics Reference

配置圖形上下文

在調用 drawRect: 方法以前,視圖對象已經建立了一個圖形上下文而且將其置爲當前的上下文。它只存在於drawRect:方法調用期間。能夠經過調用UIGraphicsGetCurrentContext函數來獲取圖形上下文的一個引用。方法返回一個CGContextRef類型對象的引用,該對象傳遞了Core Graphics函數修改當前圖形的狀態。表1-1列出了主要的幾個方法。須要查看完整的請移步 CGContext Reference。下表還列出了UIKit替代方法。

表1-1 修改圖形狀態的Core Graphics方法

狀態 函數名 UIKit替代方法
Current transformation matrix (CTM) CGContextRotateCTM/CGContextScaleCTM/CGContextTranslateCTM/CGContextConcatCTM None
Clipping area CGContextClipToRect UIRectClip function
Line: Width, join, cap, dash, miter limit CGContextSetLineWidth/CGContextSetLineJoin/CGContextSetLineCap/CGContextSetLineDash/CGContextSetMiterLimit None
Accuracy of curve estimation CGContextSetFlatness None
Anti-aliasing setting CGContextSetAllowsAntialiasing None
Color: Fill and stroke settings CGContextSetRGBFillColor/CGContextSetRGBStrokeColor UIColor class
Alpha global value (transparency) CGContextSetAlpha None
Rendering intent CGContextSetRenderingIntent None
Color space: Fill and stroke settings CGContextSetFillColorSpace/CGContextSetStrokeColorSpace UIColor class
Text: Font, font size, character spacing, text drawing mode CGContextSetFont/CGContextSetFontSize/CGContextSetCharacterSpacing UIFont class
Blend mode CGContextSetBlendMode The UIImage class and various drawing functions let you specify which blend mode to use.

上下文中以堆棧形式保存了圖形的裝填。上下文被Quartz建立時堆棧是空的。經過調用CGContextSaveGState函數將當前圖形狀態推入堆棧。此後圖形狀態的改變會影響後續的繪製操做,但不會影響以前已如堆棧的。當完成修改後能夠經過調用CGContextRestoreGState函數來將其中堆棧中彈出。這種推入和彈出操做替代了逐個撤銷每一個狀態的操做。這也是惟一能還原到以前狀態的方法。

更多信息參考 Graphics ContextQuartz 2D

繪製路徑

路徑是一系列線和貝塞爾曲線組成的矢量形狀。UIKit中包含了UIRectFrameUIRectFill等函數用於繪製簡單的路徑(相似矩形)。Core Graphics也提供了便捷函數用於繪製簡單路徑(例如矩形和橢圓)。

更多複雜路徑,就須要使用UIBezierPath類本身畫了,或者使用函數操做Core Graphics提供的CGPathRef。儘管能夠脫離上下文繪製路徑,但最終底層仍是使用了上下文,只是被封裝了。

--- 結束 ---

原文:iOS Drawing Concepts

延伸閱讀

CTM

相關文章
相關標籤/搜索