在iOS中常用的框架是Quartz 2D,它是Core Graphics框架的一部分,是一個強大的二維圖像繪製引擎。我們日常開發所用到的UIKit的組件都是由Core Graphics框架進行繪製的。當我們導入UIKit框架時,會自動導入Core Graphics框架。 在iOS中繪圖一般分為以下幾 ...
在iOS中常用的框架是Quartz 2D,它是Core Graphics框架的一部分,是一個強大的二維圖像繪製引擎。我們日常開發所用到的UIKit的組件都是由Core Graphics框架進行繪製的。當我們導入UIKit框架時,會自動導入Core Graphics框架。
在iOS中繪圖一般分為以下幾個步驟:
1.獲取繪圖上下文
2.創建並設置路徑
3.將路徑添加到上下文
4.設置上下文的狀態
5.繪製路徑
6.釋放路徑
圖形上下文中CGContextRef代表圖形輸出設備,也就是繪製的位置,包含繪製圖形的一些設備信息,Quartz 2D中的所有對象,都最終都必須要添加到圖形上下文,這樣一來,我們在繪製圖形的時候就不必關係具體的設備信息。
基本圖形繪製
在UIKit中,已經預設為我們準備好一個圖形上下文對象,在UIView的drawrect方法中,我們可以通過通過UIKit封裝函數UIGraphicsGetCurrentContext()方法獲取到圖形上下文(註:在其它方法中無法獲取),然後只需要按照繪製圖形的步驟一步步執行即可,下麵重寫view的drawrect方法,在view上添加兩條線:
override func draw(_ rect: CGRect) { // 1.獲取圖形上下文對象 let contextRef = UIGraphicsGetCurrentContext() // 2.創建路徑對象 let path = CGMutablePath() path.move(to: CGPoint(x: 10, y: 10)) path.addLine(to: CGPoint(x: 100, y: 100)) // 這一段代碼是繪製曲線,lengths 表示的是 虛線的每段長度為5,每段間距為1,phase 如果填2,則表示虛線的第一個線段的長度為 5 - 2 let a : CGFloat = 5 let b : CGFloat = 1 let lengths = [a,b] contextRef?.setLineDash(phase: 2, lengths: lengths) path.move(to: CGPoint(x: 5, y: 20)) path.addLine(to: CGPoint(x: 100, y: 100)) // 3.添加到圖像上下文 contextRef?.addPath(path) // 4.設置圖形上下文狀態屬性 contextRef?.setStrokeColor(UIColor.red.cgColor) // 邊框顏色 contextRef?.setFillColor(UIColor.blue.cgColor) // 填充顏色,可以在封閉路徑中使用 contextRef?.setLineWidth(2) // 線條寬度 // 5.繪製 contextRef?.strokePath() }
封閉圖形繪製
上面的繪圖方式顯得有些麻煩,其實Core Graphics內部對創建路徑對象添加到上下文這兩步操作進行了封裝,可以一步完成,下麵我們用線段繪製一個簡單的矩形:
// 1.獲取圖像上下文 let contextRef = UIGraphicsGetCurrentContext() // 2.開始繪製一個正方形 contextRef?.move(to: CGPoint(x: 10, y: 10)) contextRef?.addLine(to: CGPoint(x: 110, y: 10)) contextRef?.addLine(to: CGPoint(x: 110, y: 110)) contextRef?.addLine(to: CGPoint(x: 10, y: 110)) // 第四條線我們可以直接使用這個方法繪製 contextRef?.closePath() // 3.設置相關狀態 contextRef?.setLineWidth(2) contextRef?.setStrokeColor(UIColor.red.cgColor) contextRef?.setFillColor(UIColor.blue.cgColor) // 4.開始繪製 .既有邊框(路徑),又有填充 contextRef?.drawPath(using: .fillStroke)
繪製一個矩形
上面的代碼已經相對來說簡化了不少,除了路徑之外,矩形,橢圓也都有相應的繪製方法。
// ----------- 使用addRect方法繪製 ------------ // 1.獲取圖像上下文 let contextRef = UIGraphicsGetCurrentContext() // 2.路徑 contextRef?.addRect(CGRect(x: 10, y: 10, width: 100, height: 100)) // 3.狀態 contextRef?.setFillColor(UIColor.blue.cgColor) contextRef?.setStrokeColor(UIColor.red.cgColor) // 4.繪製 contextRef?.drawPath(using: .fillStroke)
// ----------- 使用UI方法繪製 ------------ // 繪製一個填充色為紅色的矩形 UIColor.red.setFill() UIRectFill(CGRect(x: 10, y: 10, width: 100, height: 100)) // 繪製一個邊框為藍色的矩形 UIColor.blue.setStroke() UIRectFrame(CGRect(x: 20, y: 20, width: 100, height: 100))
繪製一個圓
// 1.獲取圖形上下文 let contextRef = UIGraphicsGetCurrentContext() // 2.先規定一個矩形 let rect = CGRect(x: 10, y: 10, width: 100, height: 100) // 3.添加內切圓 contextRef?.addEllipse(in: rect) // 內切圓 // 4.設置填充色 contextRef?.setFillColor(UIColor.red.cgColor) // 5.繪製 contextRef?.drawPath(using: .fill)
繪製一個弧形
// 1.獲取圖形上下文 let contextRef = UIGraphicsGetCurrentContext() // 2.添加弧度 let center = CGPoint(x: 100, y: 100) // 圓心坐標
contextRef.moveTo(center) // 加上這句代碼就可以繪製出一個扇形 let radius : CGFloat = 50 // 半徑 let startAngle : CGFloat = 0 // 開始弧度制 let endAngle : CGFloat = CGFloat(Double.pi/4) // 結束弧度制 let clockwise = true // 是否逆時針 contextRef?.addArc(center: center, radius: CGFloat(radius), startAngle: startAngle, endAngle: endAngle, clockwise: clockwise) // 3.設置填充色 contextRef?.setFillColor(UIColor.red.cgColor) // 4.繪製 contextRef?.drawPath(using: .fill)
漸變色填充
在上面的Demo中我們可以看到如何設置填充顏色,事實上很多時候純色的填充並不能滿足我們的足球,例如有的時候我們需要繪製一些圖形可能需要設置一個漂亮的背景,如果UI MM不給切圖的話,這個時候我們可能就會選擇漸變填充的方式。
Quartz 2D的漸變方式分為兩種:
a.線性漸變線:漸變色以直線方式從開始位置逐漸向結束位置漸變
b.徑向漸變:以中心點為圓心從起始漸變色向四周輻射,直到終止漸變色。
要做漸變則必須先設置從開始位置到結束位置的漸變顏色,UI MM可定對於漸變色設置並不陌生,只需要在指定位置指定不同的顏色,剩下的事情交給系統處理就行。如下圖,在起始位置,3/10位置,結束位置指定了三種顏色就形成了由三種顏色組成的漸變色。
另外,在iOS中繪製漸變還需要註意一點就是指定顏色空間,所謂顏色空間就是不同顏色在不同的維度上取值,最終組成一種顏色的過程。就拿RGB來說,如果將紅色,綠色,藍色看成是x、y、z軸坐標系,那麼在三個坐標上分別取0-255範圍內的不同值則可以組成各類顏色。當然,不同顏色空間的坐標系也是不同的,也就是說顏色表示的方式是不同的,常用的顏色空間除了RGB還有CMYK(印刷業常用這種顏色模式)、Gray.
在使用Quartz 2D繪圖時我們的顏色除了使用常規的方法(如CGContextSetRGBFillColor(CGContextRef context, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)方法)設置RGB和透明度之外,有時還會遇到顏色參數是一個數組情況。如使用顏色空間填充時用到的CGContextSetFillColor(CGContextRef context, const CGFloat *components)方法,這個時候components數組中具體是如何存儲顏色就要根據顏色空間而定,如果顏色空間使用RGB,則數組中的元素四個為一組,分別是red、green、blue、alpha;如果使用的是CMYK顏色空間,那麼數組中的元素五個為一組,分別是cyan、magenta、yellow、black、alpha。
下麵,分別使用一下這兩種漸變的方式:
1.線性漸變
override func draw(_ rect: CGRect) { // 獲取上下文 let contextRef = UIGraphicsGetCurrentContext() // 使用RGB顏色空間 let colorSpace = CGColorSpaceCreateDeviceRGB() // 指定漸變色 /* colorSpace : 顏色空間 colorComponents : 顏色數組,由於指定了為RGB顏色空間,那麼四個數組元素就表示一個顏色(red\green\blue\alpha),如果有三個顏色,那麼這個數組中就要有 3 * 4 個元素。 locations : 顏色所在的位置 0 - 1,這個數組的個數不小於顏色的個數 count : 漸變的個數,等於locations的count */ let colorComponents : [CGFloat] = [248.0/255.0,86.0/255.0,86.0/255.0,1, 249.0/255.0,127.0/255.0,127.0/255.0,1, 1.0,1.0,1.0,1.0] let locations : [CGFloat] = [0,0.3,1.0] let gradient = CGGradient(colorSpace: colorSpace, colorComponents: colorComponents, locations: locations, count: 3) // 繪製線性顏色漸變 /* start : 起始坐標 end : 結束坐標 options :繪製方式,kCGGradientDrawsBeforeStartLocation 開始位置之前就進行繪製,到結束位置之後不再繪製, kCGGradientDrawsAfterEndLocation開始位置之前不進行繪製,到結束點之後繼續填充 */ contextRef?.drawLinearGradient(gradient!, start: CGPoint(x : 0, y : 20), end: CGPoint(x : 0, y : self.frame.size.height - 40), options: .drawsBeforeStartLocation) }
2.徑向漸變
override func draw(_ rect: CGRect) { // 獲取上下文 let contextRef = UIGraphicsGetCurrentContext() // 使用RGB顏色空間 let colorSpace = CGColorSpaceCreateDeviceRGB() // 指定漸變色 /* colorSpace : 顏色空間 colorComponents : 顏色數組,由於指定了為RGB顏色空間,那麼四個數組元素就表示一個顏色(red\green\blue\alpha),如果有三個顏色,那麼這個數組中就要有 3 * 4 個元素。 locations : 顏色所在的位置 0 - 1,這個數組的個數不小於顏色的個數 count : 漸變的個數,等於locations的count */ let colorComponents : [CGFloat] = [248.0/255.0,86.0/255.0,86.0/255.0,1, 249.0/255.0,127.0/255.0,127.0/255.0,1, 1.0,1.0,1.0,1.0] let locations : [CGFloat] = [0,0.3,1.0] let gradient = CGGradient(colorSpace: colorSpace, colorComponents: colorComponents, locations: locations, count: 3) /*繪製徑向漸變 context:圖形上下文 gradient:漸變色 startCenter:起始點位置 startRadius:起始半徑(通常為0,否則在此半徑範圍內容無任何填充) endCenter:終點位置(通常和起始點相同,否則會有偏移) endRadius:終點半徑(也就是漸變的擴散長度) options:繪製方式,kCGGradientDrawsBeforeStartLocation 開始位置之前就進行繪製,但是到結束位置之後不再繪製, kCGGradientDrawsAfterEndLocation開始位置之前不進行繪製,但到結束點之後繼續填充 */ let center = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2) contextRef?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0, endCenter: center, endRadius: 30, options: .drawsAfterEndLocation) }
3.漸變填充
上面我們只是繪製漸變到圖形上下文,實際開發中有時候我們還需要填充對應的漸變色,例如現在繪製了一個矩形,如何填充成漸變色呢?在此可以利用漸變裁切來完成(當然利用層CALayer更加方便),特別說明一下區域裁切並不僅僅適用於漸變填充,對於其他圖形繪製仍然適用,並且註意裁切只能限於矩形裁切。
override func draw(_ rect: CGRect) { // 獲取上下文 let contextRef = UIGraphicsGetCurrentContext() // 使用RGB顏色空間 let colorSpace = CGColorSpaceCreateDeviceRGB() //裁切還可以使用UIKit中對應的方法 UIRectClip(CGRect(x: 0, y: 20, width: self.frame.size.width, height: self.frame.size.height - 40)) // 指定漸變色 /* colorSpace : 顏色空間 colorComponents : 顏色數組,由於指定了為RGB顏色空間,那麼四個數組元素就表示一個顏色(red\green\blue\alpha),如果有三個顏色,那麼這個數組中就要有 3 * 4 個元素。 locations : 顏色所在的位置 0 - 1,這個數組的個數不小於顏色的個數 count : 漸變的個數,等於locations的count */ let colorComponents : [CGFloat] = [248.0/255.0,86.0/255.0,86.0/255.0,1, 249.0/255.0,127.0/255.0,127.0/255.0,1, 1.0,1.0,1.0,1.0] let locations : [CGFloat] = [0,0.3,1.0] let gradient = CGGradient(colorSpace: colorSpace, colorComponents: colorComponents, locations: locations, count: 3) // 繪製線性顏色漸變 /* start : 起始坐標 end : 結束坐標 options :繪製方式,kCGGradientDrawsBeforeStartLocation 開始位置之前就進行繪製,到結束位置之後不再繪製, kCGGradientDrawsAfterEndLocation開始位置之前不進行繪製,到結束點之後繼續填充 */ contextRef?.drawLinearGradient(gradient!, start: CGPoint(x : 0, y : 20), end: CGPoint(x : 0, y : self.frame.size.height - 40), options: .drawsBeforeStartLocation) }