| 做者:Lorenzo Boarohtml
| 連接:https://www.raywenderlich.com/349664-core-graphics-tutorial-arcs-and-paths算法
| 公衆號:https://mp.weixin.qq.com/s/hhF7hO5xQWlYpqa7cg2zfgswift
在本教程中,咱們將學習如何繪製圓弧和路徑。特別是,咱們將 Grouped TableView 的每一個頁腳的底部添加整齊的弧線、線性漸變和適合弧形曲線的陰影,來美化咱們的 table view。全部這些都是經過使用 Core Graphics 的強大功能實現的!xcode
在本教程中,咱們將使用 LearningAgenda 示例程序,這是一個 iOS 應用程序,演示了咱們要學習的內容。微信
首先下載初始工程(https://koenig-media.raywenderlich.com/uploads/2019/01/LearningAgenda-2.zip)。下載後,在 Xcode 中打開LearningAgenda.xcodeproj。框架
爲了讓咱們更專一於主要內容,初始項目已設置好了與圓弧和路徑無關的全部內容。ide
構建並運行應用程序,咱們將看到如下界面:函數
如圖所示,有一個分組的 table view,包含兩個部分,每一個部分都有一個標題和三行內容。咱們在這裏要作的全部工做就是在每一個部分下方建立弧形頁腳。學習
在實現功能以前,咱們須要建立並設置一個自定義的頁腳,這將做爲咱們後續工做的基礎。ui
要爲閃亮的新頁腳建立類,能夠右鍵單擊 LearningAgenda 文件夾,而後選擇「新建文件」。 接下來,選擇 Swift File 並將文件命名爲 CustomFooter.swift。
切換到 CustomFooter.swift 文件並使用如下代碼替換其內容:
import UIKit
class CustomFooter: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
isOpaque = true
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
UIColor.red.setFill()
context.fill(bounds)
}
}
複製代碼
這裏,咱們重寫 init(frame:)
以設置 isOpaquetrue
。咱們還將背景顏色設置爲 clear
。
注意:當視圖徹底或部分透明時,不該使用
isOpaque
屬性。不然,結果可能沒法預測。
咱們一樣重寫 init?(coder:)
,由於它是必需的,可是咱們不提供任何實現,由於咱們不會在 Interface Builder
中使用自定義頁腳視圖。
draw(_:)
使用 Core Graphics
提供自定義 rect 的內容。咱們將紅色設置爲填充顏色以覆蓋頁腳自己的整個 bounds。
如今,打開 TutorialsViewController.swift 並將如下兩個方法添加到文件底部的 UITableViewDelegate
擴展中:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 30
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return CustomFooter()
}
複製代碼
上述方法組合造成 30 個 point 高度的自定義頁腳視圖。
構建並運行項目,若是一切正常,咱們應該看到如下內容:
好了,既然咱們已經有了一個佔位符視圖,那麼如今是時候了。但首先咱們來設定一個目標。
上圖有如下幾點能夠注意一下:
圓弧是表示圓的一部分的曲線。在上面這種狀況下,頁腳視圖底部所需的圓弧是一個很是大的圓的頂部,具備很是大的半徑,從某個起始角度到某個結束角度。
那咱們如何向 Core Graphics 描述這個弧?咱們將使用 CGContext
的 addArc(center:radius:startAngle:endAngle:clockwise:)
方法。該方法須要如下五個輸入參數:
可是咱們又該如何去設置這些值呢?
咱們須要一些簡單的數學知識,並計算出全部這些值!
咱們知道的第一件事是想要繪製弧的邊界框的大小:
咱們知道的第二件事是一個有趣的數學定理,稱爲相交和絃定理。基本上,這個定理指出,若是你在一個圓中繪製兩個交叉的和絃,第一個和絃的分段的乘積將等於第二個和絃的分段的乘積。請記住,和絃是鏈接圓中兩個點的線。
注意:若是咱們想了解其緣由,請訪問 http://www.mathopenref.com/chordsintersecting.html - 它有一個很酷的小型 JavaScript 演示,咱們能夠直接使用。
有了這兩點知識,看看若是咱們畫出以下兩個和絃時會發生什麼:
所以,繪製一條線鏈接弧形矩形的底點和從弧形頂部向下到圓形底部的另外一條線。
若是咱們這樣作,知道了 a,b 和 c,就能夠得出 d。
因此 d 的計算公式是:(a * b) / c。用它代替,會是:
// Just substituting...
let d = ((arcRectWidth / 2) * (arcRectWidth / 2)) / (arcRectHeight);
// Or more simply...
let d = pow(arcRectWidth, 2) / (4 * arcRectHeight);
複製代碼
如今咱們知道了 c 和 d,就可使用如下公式計算半徑:(c + d) / 2:
// Just substituting...
let radius = (arcRectHeight + (pow(arcRectWidth, 2) / (4 * arcRectHeight))) / 2;
// Or more simply...
let radius = (arcRectHeight / 2) + (pow(arcRectWidth, 2) / (8 * arcRectHeight));
複製代碼
如今咱們已經知道了半徑,只需從陰影矩形的中心點減去半徑便可得到中心:
let arcCenter = CGPoint(arcRectTopMiddleX, arcRectTopMiddleY - radius)
複製代碼
一旦知道了中心點,半徑和圓弧矩形,就能夠用一些三角函數計算起點和終點角度:
咱們首先計算出圖中所示的角度。若是咱們還記得 SOHCAHTOA
(https://en.wikipedia.org/wiki/Mnemonics_in_trigonometry#SOH-CAH-TOA),可能會想起角度的餘弦等於三角形相鄰邊緣的長度除以斜邊的長度。
換句話說,cosine(angle) = (arcRectWidth / 2) / radius
。所以,爲了獲得角度,咱們只須要取餘弦,它是餘弦的倒數:
let angle = acos((arcRectWidth / 2) / radius)
複製代碼
如今咱們知道了這個角度,得到起點和終點角度應該至關簡單:
如今咱們瞭解瞭如何去作,就能夠將它們寫到一個函數裏去了。
注意:順便說一句,使用
CGContext
類型中提供的addArc(tangent1End:tangent2End:radius:)
方法,能夠更簡單地繪製這樣的弧。
咱們所作的第一件事是將度數轉換爲弧度的方法。爲此,將使用在 iOS 10 和 macOS 10.12 中引入的 Foundation Units 和 Measurements API。
Foundation 框架提供了一種使用和表示物理量的強大方法。除角度外,它還提供了幾種內置單元類型,如速度,持續時間等。
打開 Extensions.swift
並將如下代碼粘貼到文件末尾:
typealias Angle = Measurement<UnitAngle>
extension Measurement where UnitType == UnitAngle {
init(degrees: Double) {
self.init(value: degrees, unit: .degrees)
}
func toRadians() -> Double {
return converted(to: .radians).value
}
}
複製代碼
在上面的代碼中,咱們能夠在 Measurement
類型上定義一個擴展,將其用法限制爲角度單位。 init(degrees:)
僅適用於度數角度。toRadians()
容許咱們將度數轉換爲弧度。
注意:也可使用公式
radians = degrees * π / 180
將度數轉換爲弧度,反之亦然。
保留在 Extensions.swift 文件中,找到 CGContext
的擴展塊。在最後一個花括號以前,粘貼如下代碼:
static func createArcPathFromBottom(
of rect: CGRect,
arcHeight: CGFloat,
startAngle: Angle,
endAngle: Angle
) -> CGPath {
// 1
let arcRect = CGRect(
x: rect.origin.x,
y: rect.origin.y + rect.height,
width: rect.width,
height: arcHeight)
// 2
let arcRadius = (arcRect.height / 2) + pow(arcRect.width, 2) / (8 * arcRect.height)
let arcCenter = CGPoint(
x: arcRect.origin.x + arcRect.width / 2,
y: arcRect.origin.y + arcRadius)
let angle = acos(arcRect.width / (2 * arcRadius))
let startAngle = CGFloat(startAngle.toRadians()) + angle
let endAngle = CGFloat(endAngle.toRadians()) - angle
let path = CGMutablePath()
// 3
path.addArc(
center: arcCenter,
radius: arcRadius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: false)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minY, y: rect.maxY))
// 4
return path.copy()!
}
複製代碼
到這已經進展了很多,如下是具體描述:
arcRect
。注意:與
CGContext
擴展中可用的其餘函數不一樣,createArcPathFromBottom(of:arcHeight:startAngle:endAngle:)
返回CGPath
。這是由於路徑將被重複使用屢次。稍後會詳細介紹。
如今咱們有了一個輔助方法來繪製弧線,如今是時候用咱們的新弧形替換你的矩形頁腳視圖了。
打開 CustomFooter.swift 並使用如下代碼替換 draw(_:)
:
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
let footerRect = CGRect(
x: bounds.origin.x,
y: bounds.origin.y,
width: bounds.width,
height: bounds.height)
var arcRect = footerRect
arcRect.size.height = 8
context.saveGState()
let arcPath = CGContext.createArcPathFromBottom(
of: arcRect,
arcHeight: 4,
startAngle: Angle(degrees: 180),
endAngle: Angle(degrees: 360))
context.addPath(arcPath)
context.clip()
context.drawLinearGradient(
rect: footerRect,
startColor: .rwLightGray,
endColor: .rwDarkGray)
context.restoreGState()
}
複製代碼
在一般的 Core Graphics 設置以後,咱們將爲整個頁腳視圖區域和想要圓弧的區域建立一個邊界框。
而後,經過調用剛剛編寫的 createArcPathFromBottom(of:arcHeight:startAngle:endAngle:)
靜態方法得到弧形路徑。而後,咱們能夠將路徑添加到上下文並剪切到該路徑。
後續進一步的繪圖將限於該路徑。而後,可使用 Extensions.swift 中的 drawLinearGradient(rect:startColor:endColor:)
繪製從淺灰色到深灰色的漸變。
構建並運行應用程序。若是一切正常,咱們應該看到如下界面:
看起來不錯,但咱們須要再完善一下。
在 CustomFooter.swift 中,將如下內容添加到 draw(_:)
的底部:
context.addRect(footerRect)
context.addPath(arcPath)
context.clip(using: .evenOdd)
context.addPath(arcPath)
context.setShadow(
offset: CGSize(width: 0, height: 2),
blur: 3,
color: UIColor.rwShadow.cgColor)
context.fillPath()
複製代碼
這裏有一個新的,很是重要的概念。
要繪製陰影,請啓用陰影繪製,而後填充路徑。而後 Core Graphics 將填充路徑並在下方繪製適當的陰影。
可是咱們已經使用漸變填充了路徑,所以並不但願用顏色覆蓋該區域。
嗯,這聽起來像裁剪工做!咱們能夠設置裁剪,以便 Core Graphics 僅繪製頁腳區域外部分。而後,咱們能夠告訴它填充頁腳區域並繪製陰影。因爲設置了裁剪,頁腳區域填充將被忽略,但陰影將顯示。
可是咱們沒有這樣一條路徑 - 惟一的路徑是頁腳區域而不是外部區域。
使用 Core Graphics 的一些功能,咱們能夠輕鬆地根據內部獲取外部路徑。咱們只需向上下文添加多個路徑,而後使用 Core Graphics 提供的特定規則添加裁剪。
當咱們向上下文添加多個路徑時,Core Graphics 須要某種方式來肯定是否應該填充哪些點。例如,你能夠有一個圓圈形狀,其中外部是填充但內部是空的,或者是圓環形狀,其中內部填充但外部是空的。
咱們能夠指定不一樣的算法讓 Core Graphics 知道如何處理它。本教程中將使用的算法是 EO,甚至是 even-odd。
在 EO 中,對於任何給定點,Core Graphics 將從該點繪製一條線到繪圖區域的外部。若是該線穿過奇數個點,它將被填充。若是它穿過偶數個點,則不會被填充。
如下是 Quartz2D Programming Guide 中的圖示:
所以,經過使用 EO 變體,咱們告訴 Core Graphics,即便已經向上下文添加了兩條路徑,它也應該將其視爲遵循 EO 規則的一條路徑。所以,外部部分,即整個頁腳矩形,應該被填充,但內部部分,即弧形路徑則不該該。咱們告訴 Core Graphics 剪切到該路徑並僅在外部區域繪製。
設置裁剪區域後,添加弧的路徑,設置陰影並填充圓弧。固然,因爲它被剪裁,實際上什麼都沒有被填充,但陰影仍將被繪製在外部區域!
構建並運行項目,若是一切順利,咱們如今應該看到頁腳下方的陰影:
恭喜!咱們已使用 Core Graphics 建立了自定義 table view 的頁腳視圖!
咱們能夠下載項目的完整版本。
經過本教程,咱們已經學習瞭如何建立圓弧和路徑。如今,能夠將這些概念直接應用到應用中!
若是您想了解有關 Core Graphics 的更多信息,請查看 Quartz 2D Programming Guide。
歡迎關注咱們的公衆號:iOS-Tips,也歡迎加入咱們的羣組討論問題。能夠加微信 coldlight_hh
/wsy9871
進入咱們的 iOS
/flutter
微信羣。