前言: 本篇簡(jiǎn)單介紹使用Quartzs 2D來繪圖.如果你在網(wǎng)上搜索關(guān)于iOS開發(fā)中對(duì)于圖片的處理(縮放...),或者運(yùn)用截取屏幕或者某一部分內(nèi)容來實(shí)現(xiàn)一些動(dòng)畫交互(比如實(shí)現(xiàn)tableViewCell, collectionView的移動(dòng)效果...), 以及自定義一些控件(比如圓形進(jìn)度條...)那么大多數(shù)的處理都會(huì)涉及到繪圖的處理, 也許很多功能在你需要實(shí)現(xiàn)的時(shí)候, 搜索一下可以找到一些解決問題的代碼, 但是或許在使用很久之后還是不明白其中的知識(shí)點(diǎn). 本篇大多內(nèi)容源于對(duì)官方文檔"Quartz 2D Programming Guide"的理解.
首先對(duì)于Quartz 2D就不多做介紹了,(處理路徑的繪圖, 透明度繪圖, 遮蓋,陰影, 顏色管理, 防鋸齒渲染, 生成PDF...) 感興趣的朋友可以查查相關(guān)的資料
1. 了解Quartz 2D的繪圖機(jī)制
- 和現(xiàn)實(shí)中的繪圖一樣, 首先需要一張"畫布", 在iOS中被稱為 "圖形上下文", 這個(gè)圖形上下文包含了設(shè)備的信息等很多系統(tǒng)準(zhǔn)備好的內(nèi)容
- 在上下文中運(yùn)用一些方法來設(shè)置我們要繪制的內(nèi)容和屬性,來實(shí)現(xiàn)繪圖
-
Quartz 2D類似打印機(jī), 對(duì)不同的內(nèi)容的繪圖順序不同, 得到的結(jié)果就不相同
這里有一張官方的圖的解釋,很直接
painters_model.gif
2.獲取上下文
- 重寫UIView的drawRect()方法, 當(dāng)這個(gè)view的內(nèi)容需要更新的時(shí)候會(huì)調(diào)用這個(gè)方法, 所以系統(tǒng)在這個(gè)方法里面會(huì)默認(rèn)提供一個(gè)上下文, 可以通過UIGraphicsGetCurrentContext() 獲取到
- 在其他地方可以通過UIGraphicsBeginImageContextWithOptions()開啟一個(gè)上下文, 然后通過 UIGraphicsGetCurrentContext() 可以獲取到
- 關(guān)于drawRect()方法(處理不當(dāng)時(shí)有人稱為"內(nèi)存惡魔")
// 調(diào)用setNeedsDisplay()這個(gè)函數(shù)會(huì)觸發(fā)drawRect方法, 當(dāng)這個(gè)函數(shù)被調(diào)用的時(shí)候, 系統(tǒng)會(huì)通知這個(gè)UIView整個(gè)界面需要重繪. 但是這個(gè)函數(shù)會(huì)直接返回, 但界面的變化不會(huì)馬上返回, 需要等到下個(gè)生命周期進(jìn)行重繪
// 如果只有一部分需要重繪 可以調(diào)用這個(gè)方法 setNeedsDisplayInRect(rect: CGRect)
// 這個(gè)方法里面應(yīng)該只做與界面的繪制有關(guān)的工作, 不應(yīng)該進(jìn)行任何的數(shù)據(jù)處理或者程序的邏輯相關(guān)的任務(wù), 以保證盡快執(zhí)行完畢
3. 坐標(biāo)系
- 和數(shù)學(xué)中的直角坐標(biāo)系相同, 原點(diǎn)在左下角, UIKit中的坐標(biāo)系的原點(diǎn)在左上角, 所以使用Quartz 2D繪圖的時(shí)候要注意進(jìn)行坐標(biāo)系的轉(zhuǎn)換
4. 內(nèi)存管理
- 因?yàn)槭褂玫腃語言的東西, 編譯器并沒有給我們自動(dòng)管理內(nèi)存, 在oc中需要我們自己來管理, 即當(dāng)調(diào)用含有create或者copy的函數(shù)獲得上下文的時(shí)候, 需要在使用完后手動(dòng)release, 但在swift中我們不用管理了(而且C語言的函數(shù)在swift中看上去也比較習(xí)慣)
5. 繪制我們需要的內(nèi)容
-
5.1 繪制路徑
- 首先需要我們畫出路徑 -> 指定起始點(diǎn)到另一個(gè)點(diǎn), 然后兩點(diǎn)之間可以使用直線或者曲線來連接, 就形成了一條路徑, 例如使用下面的函數(shù)來實(shí)現(xiàn)
```CGContextMoveToPoint() -> 指定起始點(diǎn)或者移動(dòng)到新的點(diǎn)
CGContextAddLineToPoint() -> 從當(dāng)前的點(diǎn)到指定的點(diǎn)之間畫一條線
CGContextAddArc() -> 圓弧
CGContextAddArcToPoint() -> 會(huì)在當(dāng)前點(diǎn)和指定點(diǎn)與坐標(biāo)系平行的直線做切線畫圓弧
CGContextAddCurveToPoint() -> 畫曲線(bezier)
CGContextClosePath() -> 將起始點(diǎn)和結(jié)束點(diǎn)連接起來形成封閉的路徑
CGContextAddEllipseInRect() -> 橢圓
```- 然后設(shè)置需要繪制的路徑的屬性 -> 設(shè)置顏色,透明度,寬度, 繪制模式...例如使用如下的函數(shù)來實(shí)現(xiàn)
```
CGContextSetLineWidth() -> 線寬度
CGContextSetLineJoin() -> 線的連接點(diǎn)的樣式
CGContextSetLineCap() -> 端點(diǎn)的樣式
CGContextSetLineDash() -> 虛線
CGContextSetStrokeColorWithColor() -> stroke模式時(shí)的顏色
CGContextSetFillColorWithColor() -> fill模式時(shí)的顏色
```
* 繪制內(nèi)容, 注意會(huì)涉及到兩種方式: fill 和 stroke, 這里解釋一下兩者的區(qū)別
- 當(dāng)使用stroke方式 -> "描邊"即只繪制路徑
當(dāng)使用fill方式"填充"即繪制路徑包括的所有區(qū)域(所有路徑都會(huì)被當(dāng)作closePath處理)
-
Filling有兩種模式
nonzero winding number(默認(rèn)) -> 如果兩個(gè)路徑有重疊的時(shí)候, 繪制方向相同的話, 那么重疊部分的繪制可能不是我們希望的
even-odd -> 不受繪制方向的影響
CGContextEOFillPath
CGContextFillPath
CGContextFillRect
CGContextFillRects
CGContextFillEllipseInRectCGContextStrokePath(context) CGContextFillPath(context) 示例
stroke模式繪制線
```
override func drawRect(rect: CGRect) {
// Drawing code
// 獲取當(dāng)前上下文
let context = UIGraphicsGetCurrentContext()
let strokeColor = UIColor.blueColor()
// 設(shè)置stroke模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
CGContextSetStrokeColorWithColor(context, strokeColor.CGColor)
// 設(shè)置線的寬度
CGContextSetLineWidth(context, 5.0)
// 設(shè)置連接處樣式...還可以設(shè)置很多其他的屬性
CGContextSetLineJoin(context, CGLineJoin.Round)
// 起始點(diǎn)
CGContextMoveToPoint(context, 10.0, 10.0)
// 在點(diǎn)(10.0, 10.0)和(10.0, 80.0)之間畫一條線 所以點(diǎn)(10.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
CGContextAddLineToPoint(context, 10.0, 80.0)
//在點(diǎn)(10.0, 80.0)和(80.0, 80.0)之間畫一條線 所以點(diǎn)(80.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
CGContextAddLineToPoint(context, 80.0, 80.0)
// 設(shè)置為封閉路徑, 會(huì)將首尾點(diǎn)連接起來
// CGContextClosePath(context)
// 繪制當(dāng)前的路徑
CGContextStrokePath(context)
}
```


fill模式繪制相同的路徑
let fillColor = UIColor.blueColor()
// 設(shè)置fill模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
CGContextSetFillColorWithColor(context, fillColor.CGColor)
// 起始點(diǎn)
CGContextMoveToPoint(context, 10.0, 10.0)
// 在點(diǎn)(10.0, 10.0)和(10.0, 80.0)之間畫一條線 所以點(diǎn)(10.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
CGContextAddLineToPoint(context, 10.0, 80.0)
//在點(diǎn)(10.0, 80.0)和(80.0, 80.0)之間畫一條線 所以點(diǎn)(80.0, 80.0)被設(shè)置為下一個(gè)起始點(diǎn)
CGContextAddLineToPoint(context, 80.0, 80.0)
// CGContextClosePath(context)
// 繪制當(dāng)前的路徑
CGContextFillPath(context)
```

stroke模式繪制圓
// 設(shè)置stroke模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
CGContextSetStrokeColorWithColor(context, strokeColor.CGColor)
// 設(shè)置線的寬度
CGContextSetLineWidth(context, 5.0)
// 設(shè)置連接處樣式
CGContextSetLineJoin(context, CGLineJoin.Round)
// 起始點(diǎn)
let circleCenter = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
CGContextMoveToPoint(context, circleCenter.x*2, circleCenter.y)
// 畫圓
// x -> 圓心的x坐標(biāo)
// y -> 圓心的y坐標(biāo)
// radius -> 圓的半徑
// startAngle -> 起始角度 (弧度制) 所以上面調(diào)整了起始點(diǎn)
// endAngle -> 結(jié)束角度(弧度制)
CGContextAddArc(context, circleCenter.x, circleCenter.y, rect.width * 0.5, 0.0, CGFloat(M_PI) * 2, 0)
CGContextStrokePath(context)

fill方式繪制圓
// 設(shè)置fill模式的顏色使用CGColor(這里涉及到顏色和顏色空間的概念)
CGContextSetFillColorWithColor(context, fillColor.CGColor)
// 起始點(diǎn)
let circleCenter = CGPoint(x: rect.width * 0.5, y: rect.height * 0.5)
CGContextMoveToPoint(context, circleCenter.x*2, circleCenter.y)
// 畫圓
// x -> 圓心的x坐標(biāo)
// y -> 圓心的y坐標(biāo)
// radius -> 圓的半徑
// startAngle -> 起始角度 (弧度制) 所以上面調(diào)整了起始點(diǎn)
// endAngle -> 結(jié)束角度(弧度制)
CGContextAddArc(context, circleCenter.x, circleCenter.y, rect.width * 0.5, 0.0, CGFloat(M_PI) * 2, 0)
// 繪制當(dāng)前的路徑
CGContextFillPath(context)

-----
#####僅僅介紹了上面的幾種看上去很簡(jiǎn)單的繪制方法, 也許你會(huì)覺得沒有什么實(shí)際用處, 但是實(shí)際上是很有用處的, 因?yàn)榈浆F(xiàn)在為止, 你可以使用Quartzs 2D來實(shí)現(xiàn)自定義的各種圓形進(jìn)度條了,(可能使用到stroke和fill的混合模式), 只需要在提供的progress屬性設(shè)置時(shí)調(diào)用setNeedsDisplay()就可以觸發(fā)drawRect()方法進(jìn)行重繪, 而在這里面你就可以利用progress來改變繪制的內(nèi)容了, 從而實(shí)現(xiàn)進(jìn)度條的效果
---
> 限于篇幅和時(shí)間, 這篇中只是梳理了一些基本的繪圖概念和繪制簡(jiǎn)單的圖形(實(shí)際上看看API就可以類似的使用相關(guān)的方法繪制出其他的圖形 -- 橢圓...), 而實(shí)際上還有比較多的東西需要研究, 以后有必要在補(bǔ)上. [Demo](https://github.com/jasnig/DrawingStudy)
