Quartz 2D自定義UI控件

(一)什么是Quartz 2D?

  • Quartz 2D是一個(gè)二維繪圖引擎葡秒,支持iOS和Mac OS系統(tǒng)。

(二)Quartz 2D可以做的事情?

  1. 繪制圖形:線條眯牧、三角形蹋岩、矩形、圓形学少、弧形等
    • 手勢(shì)解鎖
    • 繪制圖表星澳,折線圖、柱狀圖旱易、餅狀圖
  2. 繪制文字
    • 涂鴉
  3. 繪制和生成圖片
    • 涂鴉
  4. 讀取和生成PDF
  5. 截圖和裁剪
    • 頭像裁剪
  6. 自定義UI控件

(三)Quartz 2D在iOS開(kāi)發(fā)中的價(jià)值

在開(kāi)發(fā)iOS程序時(shí),iOS提供了UIKit框架讓我們可以利用UIKit框架中的各種各樣的UI控件去搭建美觀的UI界面腿堤。比如:UILabel阀坏、UIImageView、UIButton笆檀。利用這些UI控件我們可以實(shí)現(xiàn)一些常見(jiàn)的界面忌堂。但是有些時(shí)候,有些UI界面極其復(fù)雜酗洒,用普通的UI控件無(wú)法實(shí)現(xiàn)士修,這是可以利用Quartz 2D技術(shù)畫(huà)出我們需要的復(fù)雜結(jié)構(gòu)的UI控件。
所以Quartz 2D的最大的價(jià)值就是自定義實(shí)現(xiàn)我們需要的但是系統(tǒng)沒(méi)有提供的UI控件

(四)圖形上下文(理解成畫(huà)板)

在Quartz 2D中的重要概念之一就是圖形上下文樱衷,我們可以理解成畫(huà)畫(huà)的畫(huà)板
圖形上下文是CGContextRef類(lèi)型

(五)圖形上下文的作用

  1. 保存繪圖信息棋嘲、繪圖狀態(tài)
  2. 決定繪制的輸出目標(biāo)(繪制到什么地方去?)
    • 輸出目標(biāo)可以是PDF文件矩桂、Bitmap沸移、顯示器的窗口
  3. 相同的繪圖序列,指定不同的Graphics Context侄榴,就可將相同的圖像繪制到不同的目標(biāo)上

(六)Graphics Context的類(lèi)型

  • Bitmap Graphics Context
  • PDF Graphics Context
  • Window Graphics Context
  • Layer Graphics Context
  • Printer Graphics Context

(七)自定義UIView

利用Quartz 2D繪制東西到UIView上需要兩個(gè)前提條件:

  1. 需要一個(gè)圖形上下文雹锣。(保存繪制信息、決定繪到什么地方去)
  2. 該圖形上下文跟UIView關(guān)聯(lián)癞蚕,如此才能將內(nèi)容繪制到UIIView上去

實(shí)現(xiàn)步驟:

  1. 自定義View繼承自UIView
  2. 實(shí)現(xiàn)系統(tǒng)的drawRect方法
  3. drawRect方法中
    3.1. 獲取跟當(dāng)前View相關(guān)聯(lián)的圖形上下文
    3.2. 繪制相應(yīng)的圖形內(nèi)容
    3.3. 利用圖形上下文將繪制的所有內(nèi)容渲染顯示到View上面

(八)drawRect

為什么要實(shí)現(xiàn)drawRect方法才能繪圖到View上蕊爵?

  • 因?yàn)樵?code>drawRect方法中才能取得跟View相關(guān)聯(lián)的圖形上下文
  • 我們需要通過(guò)在上下文中繪制內(nèi)容,然后把上下文中的內(nèi)容渲染到viewlayer

drawRect方法在什么時(shí)候調(diào)用桦山?

  • 當(dāng)view第一次顯示到屏幕上時(shí)(被添加到UIWindow上時(shí)調(diào)用)
  • 調(diào)用viewsetNeedsDisplay或者setNeedsDisplayRect時(shí)調(diào)用

(九)Quartz 2D繪圖代碼的步驟

1. 獲得圖形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();

2. 拼接路徑(下面代碼是搞一條線段)
CGContextMoveToPoint(ctx, 10, 10);
CGContextAddLineToPoint(ctx, 100, 100);

3. 繪制路徑
CGContextStrokePath(ctx); // CGContextFillPath(ctx);

(十)常用拼接路徑的函數(shù)

新建一個(gè)起點(diǎn)
void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)

添加新的線段到某個(gè)點(diǎn)
void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)

添加一個(gè)矩形
void CGContextAddRect(CGContextRef c, CGRect rect)

添加一個(gè)橢圓
void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)

添加一個(gè)圓弧
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,
CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)

(十一)常用繪制路徑的函數(shù)

Mode參數(shù)決定繪制的模式
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode)

繪制空心路徑
void CGContextStrokePath(CGContextRef c)

繪制實(shí)心路徑
void CGContextFillPath(CGContextRef c)

提示:一般以CGContextDraw攒射、CGContextStroke、CGContextFill開(kāi)頭的函數(shù)恒水,都是用來(lái)繪制路徑的

(十二)上下文狀態(tài)棧

上下文狀態(tài)棧是上下文用來(lái)存儲(chǔ)路徑狀態(tài)的匆篓,這里我們通過(guò)一個(gè)例子來(lái)說(shuō)明上下文狀態(tài)棧的原理

在view上畫(huà)一個(gè)“十”字,一橫為紅色寇窑,寬度為10鸦概,一豎為綠色,寬度20 ???

錯(cuò)誤示例:

let context = UIGraphicsGetCurrentContext()

let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 100))
path.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path.cgPath)

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 100, y: 20))
path1.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path1.cgPath)

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

context?.strokePath()

首先分析一下為什么上面這段代碼無(wú)法呈現(xiàn)出我們想要的效果

上面的代碼首先獲取和當(dāng)前view相關(guān)聯(lián)的上下文

let context = UIGraphicsGetCurrentContext()

然后,畫(huà)兩條需要的線窗市,且分別添加到上下文上

let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 100))
path.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path.cgPath)

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 100, y: 20))
path1.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path1.cgPath)

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

然后再把畫(huà)好的兩條線渲染到和當(dāng)前上下文關(guān)聯(lián)的view的layer上

context?.strokePath()

這里呈現(xiàn)的效果是兩條顏色寬度都為綠色的線


image.png

為什么是兩條綠色的線先慷?是因?yàn)樯舷挛牡臓顟B(tài)會(huì)被覆蓋,第二次設(shè)置的狀態(tài)會(huì)覆蓋第一次設(shè)置的狀態(tài)咨察,導(dǎo)致上下文中所有的線都是最后一次設(shè)置的路徑狀態(tài)(寬度為20论熙,顏色為綠色)

為了防止覆蓋的問(wèn)題,我們可以先畫(huà)好一根線摄狱,再畫(huà)第二根線

// 1. 獲取當(dāng)前view相關(guān)聯(lián)的上下文
/*
 * 獲取上下文相當(dāng)于開(kāi)辟了一塊內(nèi)存空間
 * 該內(nèi)存空間分為兩部分
 *  1. 存儲(chǔ)路徑
 *  2. 存儲(chǔ)路徑的狀態(tài)(UIColor脓诡,Width)
 */
let context = UIGraphicsGetCurrentContext()

let path = UIBezierPath()
path.move(to: CGPoint(x: 100, y: 20))
path.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path.cgPath)

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

// 取出上下文中所有繪制的路徑,并把上下文中存儲(chǔ)的路徑狀態(tài)應(yīng)用到所有的路徑上媒役,渲染到上下文關(guān)聯(lián)的view的layer上
context?.strokePath()

/************************分割線************************/

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 20, y: 100))
path1.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path1.cgPath)

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

context?.strokePath()
image.png

通過(guò)上下文狀態(tài)棧來(lái)實(shí)現(xiàn)類(lèi)似效果
首先如果我們想要在一個(gè)view上通過(guò)Quartz 2D技術(shù)畫(huà)路徑(圖)祝谚,我們需要獲取和當(dāng)前view相關(guān)聯(lián)的上下文(context)
獲取上下文相當(dāng)于在內(nèi)存中開(kāi)辟了一塊存儲(chǔ)空間。
該內(nèi)存空間分為兩部分酣衷,這里用A交惯、B表示:
A部分. 存儲(chǔ)繪制的路徑
B部分. 存儲(chǔ)路徑的狀態(tài)(路徑的顏色、路徑的寬度)

let context = UIGraphicsGetCurrentContext()

然后繪制路徑穿仪,并添加到上下文中席爽。這部分路徑會(huì)存儲(chǔ)在A部分

let path = UIBezierPath()
path.move(to: CGPoint(x: 20, y: 100))
path.addLine(to: CGPoint(x: 180, y: 100))

context?.addPath(path.cgPath)

再設(shè)置上下文中第二條路徑(豎)的狀態(tài)(顏色紅,寬度為10)啊片。這部分狀態(tài)會(huì)存儲(chǔ)在B部分

context?.setStrokeColor(UIColor.red.cgColor)
context?.setLineWidth(10)

我們先保存B部分的狀態(tài)到上下文狀態(tài)棧中

/*
 * 保存上下文中存儲(chǔ)的狀態(tài)到上下文狀態(tài)棧中(棧:先進(jìn)后出)
 * 保存了:color:red只锻,width:10到上下文狀態(tài)棧中
 */
context?.saveGState()

然后再設(shè)置第一條路徑的狀態(tài)(顏色綠,寬度20)紫谷,并渲染到當(dāng)前上下文關(guān)聯(lián)的viewlayer

context?.setStrokeColor(UIColor.green.cgColor)
context?.setLineWidth(20)

context?.strokePath()

再繪制第二條路徑炬藤,并取出上下文狀態(tài)棧中保存的第一條路徑的狀態(tài),并渲染到當(dāng)前上下文關(guān)聯(lián)的viewlayer

let path1 = UIBezierPath()
path1.move(to: CGPoint(x: 100, y: 20))
path1.addLine(to: CGPoint(x: 100, y: 180))

context?.addPath(path1.cgPath)

/*
 * 從當(dāng)前上下文的狀態(tài)棧中碴里,取出棧頂?shù)模ㄗ詈蟠嫒氲臓顟B(tài))狀態(tài)沈矿,覆蓋上下文中存儲(chǔ)的狀態(tài)
 */
context?.restoreGState()

context?.strokePath()

(十三)上下文的矩陣操作

let context = UIGraphicsGetCurrentContext()

let path = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 200, height: 130))

// 矩陣操作:平移
context?.translateBy(x: 0, y: 200)
// 矩陣操作:縮放
context?.scaleBy(x: 0.8, y: 1.3)
// 矩陣操作:旋轉(zhuǎn)
context?.rotate(by: .pi/6)

// 注意:矩陣操作要在路徑添加前進(jìn)行,否則對(duì)所添加的路徑無(wú)效
context?.addPath(path.cgPath)

context?.strokePath()
矩陣操作前

矩陣操作后

相關(guān)代碼點(diǎn)我Rб浮8拧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末根竿,一起剝皮案震驚了整個(gè)濱河市陵像,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寇壳,老刑警劉巖醒颖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壳炎,居然都是意外死亡泞歉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)腰耙,“玉大人榛丢,你說(shuō)我怎么就攤上這事⊥ε樱” “怎么了晰赞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)选侨。 經(jīng)常有香客問(wèn)我掖鱼,道長(zhǎng),這世上最難降的妖魔是什么援制? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任戏挡,我火速辦了婚禮,結(jié)果婚禮上隘谣,老公的妹妹穿的比我還像新娘。我一直安慰自己啄巧,他們只是感情好寻歧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著秩仆,像睡著了一般码泛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澄耍,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天噪珊,我揣著相機(jī)與錄音,去河邊找鬼齐莲。 笑死痢站,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的选酗。 我是一名探鬼主播阵难,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芒填!你這毒婦竟也來(lái)了呜叫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤殿衰,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后娱颊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡维蒙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年颅痊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了殖熟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡菱属,死狀恐怖纽门,靈堂內(nèi)的尸體忽然破棺而出赏陵,到底是詐尸還是另有隱情饲漾,我是刑警寧澤考传,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布僚楞,位于F島的核電站泉褐,受9級(jí)特大地震影響膜赃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悠夯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咪橙。 院中可真熱鬧,春花似錦魂奥、人聲如沸耻煤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吠勘。三九已至剧防,卻和暖如春诵姜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背暇赤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工止后, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留译株,地道東北人歉糜。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓匪补,卻偏偏與公主長(zhǎng)得像夯缺,于是被迫代替她去往敵國(guó)和親踊兜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子润文,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容