CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)

版本记录

版本号 时间
V1.0 2019.02.11 星期一

前言

quartz是一个通用的术语,用于描述在iOSMAC OS X 中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D 是一组二维绘图和渲染APICore Graphic会使用到这组APIQuartz Core专指Core Animation用到的动画相关的库、API和类。CoreGraphicsUIKit下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics数据结构和函数可以通过前缀CG来识别。在app中很多时候绘图等操作我们要利用CoreGraphic框架,它能绘制字符串、图形、渐变色等等,是一个很强大的工具。感兴趣的可以看我另外几篇。
1. CoreGraphic框架解析(一)—— 基本概览
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 类波浪线的实现
4. CoreGraphic框架解析(四)—— 基本架构补充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一个简单绘制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一个简单绘制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一个简单绘制示例 (三)
8. CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)
9. CoreGraphic框架解析 (九)—— 一个简单小游戏 (一)
10. CoreGraphic框架解析 (十)—— 一个简单小游戏 (二)
11. CoreGraphic框架解析 (十一)—— 一个简单小游戏 (三)
12. CoreGraphic框架解析 (十二)—— Shadows 和 Gloss (一)
13. CoreGraphic框架解析 (十三)—— Shadows 和 Gloss (二)

开始

首先看下写作环境

Swift 4.2, iOS 12, Xcode 10

在本教程中,您将学习如何绘制弧和路径。 特别是,您将通过在底部添加整齐的弧线,线性渐变和适合弧形曲线的阴影来增强分组表格视图的每个页脚footer。 所有这些都是通过使用Core Graphics的强大功能实现的!

在本教程中,您将使用LearningAgenda,这是一个iOS应用程序,列出了您要学习的教程和您已经学过的教程。

首先在Xcode中打开LearningAgenda.xcodeproj

为了让您专注,启动项目具有与已为您设置的弧和路径无关的所有内容。

构建并运行应用程序,您将看到以下初始屏幕:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第1张图片

如您所见,有一个分组表,包含两个部分,每个部分都有一个标题和三行。 你在这里要做的所有工作都会在每个section下方创建弧形页脚。


Enhancing the Footer

在接受挑战之前,您需要创建并设置一个自定义页脚,它将作为您未来工作的占位符。

要为闪亮的新页脚创建类,请右键单击LearningAgenda文件夹,然后选择New File。 接下来,选择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内容。 您将红色设置为填充颜色以覆盖页脚本身的整个边界。

现在,打开TutorialsViewController.swift并将以下两个方法添加到文件底部的UITableViewDelegate扩展中:

func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  return 30
}
  
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
  return CustomFooter()
}

上述方法组合形成30个高度的自定义页脚。

构建并运行项目,如果一切正常,您应该看到以下内容:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第2张图片

Back to Business

好了,既然你已经有了一个占位符视图,那么现在是时候了。 但首先,这里有一个关于你的目标的想法。

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第3张图片

请注意以下有关上图的内容:

  • 1) 页脚底部有一个整齐的弧形。
  • 2) 从浅灰色到深灰色的渐变应用于页脚。
  • 3) 弧形曲线有阴影。

The Math Behind Arcs

弧是表示圆的一部分的曲线。 在您的情况下,页脚底部所需的圆弧是一个非常大的圆的顶部,具有非常大的半径,从某个起始角度到某个结束角度。

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第4张图片

那你如何向Core Graphics描述这个弧? 那么,您将要使用的API称为addArc(center:radius:startAngle:endAngle:clockwise :)CGContext的实例方法。 该方法需要以下五个输入:

  • 圆的中心点。
  • 圆的半径。
  • 绘制线的起点,也称为起始角度。
  • 绘制线的终点,也称为结束角度。
  • 创建弧的方向。

但是,它,你不知道这些,所以你应该做什么?!

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第5张图片

这是一些简单的数学来解释的地方。 你可以从你所知道的事实上计算出所有这些!

您知道的第一件事是您想要绘制弧的边界框的大小:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第6张图片

你知道的第二件事是一个有趣的数学定理,称为Intersecting Chord Theorem。 基本上,这个定理指出,如果你在一个圆中绘制两个交叉的和弦,第一个和弦的线段的乘积将等于第二个和弦的分段的乘积。 请记住,和弦是连接圆中两个点的线。

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第7张图片

注意:如果您想了解其原因,请访问上面的链接 - 它有一个很酷的小型JavaScript演示,您可以使用它。

有了这两点知识,看看如果你画出如下两个和弦会发生什么:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第8张图片

因此,绘制一条线连接弧形矩形的底点和从弧形顶部向下到圆形底部的另一条线。

如果你这样做,你知道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);

现在您知道cd,您可以使用以下公式计算半径:(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)

一旦知道了中心点,半径和圆弧矩形,就可以用一点三角函数计算起点和终点角度:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第9张图片

您将首先计算出图中所示的角度。 如果你还记得SOHCAHTOA,你可能会想起角度的余弦等于三角形相邻边缘的长度除以斜边的长度。

换句话说,cosine(angle) = (arcRectWidth / 2) / radius。 因此,为了得到角度,你只需要取余弦,它是余弦的倒数:

let angle = acos((arcRectWidth / 2) / radius)

现在您知道了这个角度,获得起点和终点角度应该相当简单:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第10张图片

太好了! 现在您了解了如何,您可以将它们作为一个函数组合在一起。

注意:顺便说一句,使用CGContext类型中提供的addArc(tangent1End:tangent2End:radius :)方法实际上可以更简单地绘制这样的弧。


Drawing Arcs and Creating Paths

你添加的第一件事是将度数转换为弧度的方法。 为此,您将使用Apple在iOS 10macOS 10.12中引入的Foundation Units and Measurements。

注意:Foundation框架提供了一种使用和表示物理量的强大方法。 除角度外,它还提供了几种内置单元类型,如速度,持续时间等。

打开Extensions.swift并将以下代码粘贴到文件末尾:

typealias Angle = Measurement

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()!
}

这里进行详细解析:

  • 1) 此函数采用整个区域的矩形和弧度应该有多大的浮点数。 请记住,弧应位于矩形的底部。 您可以根据这两个值计算arcRect
  • 2) 然后,通过上面讨论的数学计算出半径,中心,起点和终点角度。
  • 3) 接下来,创建路径。 路径将由弧和弧上方矩形边缘周围的线组成。
  • 4) 最后,返回路径的不可变副本。 您不希望从函数外部修改路径。

注意:与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 :)绘制从浅灰色到深灰色的渐变。

再次,构建并运行应用程序。 如果一切正常,您应该看到以下屏幕:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第11张图片

看起来不错,但你需要更多地完善它。


Clipping, Paths and the Even-Odd Rule

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 Graphicsneat能力,您可以轻松地根据内部获取外部路径。您只需向上下文添加多个路径,然后使用Core Graphics提供的特定规则添加剪辑。

当您向上下文添加多个路径时,Core Graphics需要某种方式来确定应该和不应该填充哪些点。例如,你可以有一个圆圈形状,其中外部是填充但内部是空的,或者是圆环形状,其中内部填充但外部是空的。

您可以指定不同的算法让Core Graphics知道如何处理它。您将在本教程中使用的算法是EO,或者even-odd

EO中,对于任何给定点,Core Graphics将从该点绘制一条线到绘图区域的外部。如果该线穿过奇数个点,它将被填充。如果它穿过偶数个点,则不会被填充。

以下是Quartz2D Programming Guide中显示的图表:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第12张图片

因此,通过调用EO变量,您告诉Core Graphics,即使您已经向上下文添加了两条路径,它也应该将其视为遵循EO规则的一条路径。 因此,外部部分,即整个页脚矩形,应该被填充,但内部部分,即弧形路径,不应该。 您告诉Core Graphics剪切到该路径并仅在外部区域绘制。

设置剪裁区域后,添加弧的路径,设置阴影并填充弧。 当然,由于它被剪裁,实际上什么都没有被填充,但阴影仍将被绘制在外部区域!

构建并运行项目,如果一切顺利,您现在应该看到页脚下方的阴影:

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第13张图片

恭喜! 您已使用Core Graphics创建了自定义table view footers

如果您想了解有关Core Graphics的更多信息,请查看 Quartz 2D Programming Guide。

后记

本篇主要讲述了Arcs 和 Paths,感兴趣的给个赞或者关注~~~

CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)_第14张图片

你可能感兴趣的:(CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一))