| 作者:Lorenzo Boaro
| 链接:https://www.raywenderlich.com/349664-core-graphics-tutorial-arcs-and-paths
| 公众号:https://mp.weixin.qq.com/s/hhF7hO5xQWlYpqa7cg2zfg
在本教程中,我们将学习如何绘制圆弧和路径。特别是,我们将 Grouped TableView 的每个页脚的底部添加整齐的弧线、线性渐变和适合弧形曲线的阴影,来美化我们的 table view。所有这些都是通过使用 Core Graphics 的强大功能实现的!
开始
在本教程中,我们将使用 LearningAgenda 示例程序,这是一个 iOS 应用程序,演示了我们要学习的内容。
首先下载初始工程(https://koenig-media.raywenderlich.com/uploads/2019/01/LearningAgenda-2.zip)。下载后,在 Xcode 中打开 LearningAgenda.xcodeproj 。
为了让我们更专注于主要内容,初始项目已设置好了与圆弧和路径无关的所有内容。
构建并运行应用程序,我们将看到以下界面:
如图所示,有一个分组的 table view,包含两个部分,每个部分都有一个标题和三行内容。我们在这里要做的所有工作就是在每个部分下方创建弧形页脚。
增强页脚
在实现功能之前,我们需要创建并设置一个自定义的页脚,这将作为我们后续工作的基础。
要为闪亮的新页脚创建类,可以右键单击 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