CoreGraphic框架解析 (二十二) —— Gradien
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.07.29 星期三 |
前言
quartz
是一个通用的术语,用于描述在iOS
和MAC OS X
中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D
是一组二维绘图和渲染API
,Core Graphic
会使用到这组API
,Quartz Core
专指Core Animation
用到的动画相关的库、API
和类。CoreGraphics
是UIKit
下的主要绘图系统,频繁的用于绘制自定义视图。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 (二)
14. CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)
15. CoreGraphic框架解析 (十五)—— Arcs 和 Paths (二)
16. CoreGraphic框架解析 (十六)—— Lines, Rectangles 和 Gradients (一)
17. CoreGraphic框架解析 (十七)—— Lines, Rectangles 和 Gradients (二)
18. CoreGraphic框架解析 (十八) —— 如何制作Glossy效果的按钮(一)
19. CoreGraphic框架解析 (十九) —— 如何制作Glossy效果的按钮(二)
20. CoreGraphic框架解析 (二十) —— Curves and Layers(一)
21. CoreGraphic框架解析 (二十一) —— Curves and Layers(二)
开始
首先看下主要内容:
在此
Core Graphics
教程中,学习如何开发具有先进的Core Graphics
功能(例如gradients and transformations
)的现代iOS
应用。内容来自翻译。
下面看下写作环境:
Swift 5, iOS 13, Xcode 11
接着,下面就是正文啦。
在这部分中,您将进一步研究Core Graphics
,学习有关绘制渐变和通过转换操作CGContext
的知识。
Core Graphics
现在,您将离开舒适的UIKit
世界,进入Core Graphics
的底层社会。
苹果公司的这张图片从概念上描述了相关的框架:
UIKit
是顶层,也是最容易接近的。 您已使用UIBezierPath
,它是Core Graphics CGPath
的UIKit
包装。
Core Graphics
框架基于Quartz
高级绘图引擎。 它提供了低级,轻量级的2D
渲染。 您可以使用此框架来处理基于路径的绘图,transformations
,颜色管理等。
关于底层Core Graphics
对象和函数的一件事是,它们始终具有前缀CG
,因此易于识别。
到本教程结束时,您将创建一个如下所示的图形视图:
在图形视图上进行绘制之前,您需要在storyboard
中对其进行设置,并创建使过渡动画化以显示它的代码。
完整的视图层次结构如下所示:
打开起始项目,您会发现它几乎是您在上一教程中遗漏的位置。 唯一的区别是,在Main.storyboard
中,CounterView
在另一个带有黄色背景的视图的内部。 构建并运行,您将看到:
Creating the Graph
转到File ▸ New ▸ File…
,选择iOS ▸ Source ▸ Cocoa Touch Class
模板,然后单击下一步。输入名称GraphView
作为类名称,选择UIView
子类并将语言设置为Swift
。单击下一步,然后单击Create
。
现在,在Main.storyboard
中,单击Document Outline
中黄色视图的名称,然后按Enter
键将其重命名。称之为Container View
。从Counter View
下面的Container View
内部的对象库中拖动一个新的UIView
。
在Identity inspector
中将新视图的类更改为GraphView
。剩下的唯一事情就是为新的GraphView
添加约束(constraints)
,类似于在教程的上一部分中添加约束的方式:
- 选中
GraphView
时,按住Control
并拖动鼠标到中间一点,仍然在视图内,然后从弹出菜单中选择Width
。 - 在仍选择
GraphView
的情况下,按住Control
键从中心稍微向上拖动,仍然在视图内,然后从弹出菜单中选择Height
。 - 按住
Control
键从视图内部向左拖动到视图外部,然后选择Center Horizontally in Container
。 - 控制从视图内部拖动到视图外部,然后选择
Center Vertically in Container
。
在Size inspector
中编辑约束常量以使其匹配:
你的Document Outline
应该如下所示:
需要Container View
的原因是在Counter View
和Graph View
之间进行动画过渡。
转到ViewController.swift
并为Container
和Graph views
添加属性outlets
:
@IBOutlet weak var containerView: UIView!
@IBOutlet weak var graphView: GraphView!
这将为Container and Graph views
创建一个outlet
。 现在将它们连接到您在storyboard
中创建的视图。
返回Main.storyboard
,然后将Graph View
和Container View
连接到其相应的outlet
:
Setting Up the Animated Transition
仍在Main.storyboard
中时,将Tap Gesture Recognizer
从Object Library
拖动到Document Outline
中的Container
视图:
接下来,转到ViewController.swift
并将此属性添加到类的顶部:
var isGraphViewShowing = false
这只是标记当前是否显示Graph View
。
现在添加此tap
方法进行转换:
@IBAction func counterViewTap(_ gesture: UITapGestureRecognizer?) {
// Hide Graph
if isGraphViewShowing {
UIView.transition(
from: graphView,
to: counterView,
duration: 1.0,
options: [.transitionFlipFromLeft, .showHideTransitionViews],
completion: nil
)
} else {
// Show Graph
UIView.transition(
from: counterView,
to: graphView,
duration: 1.0,
options: [.transitionFlipFromRight, .showHideTransitionViews],
completion: nil
)
}
isGraphViewShowing.toggle()
}
UIView.transition(from:to:duration:options:completion :)
执行水平翻转过渡。 其他可用的过渡效果包括交叉溶解,垂直翻转和向上或向下卷曲(cross dissolve, vertical flip and curl up or down)
。 过渡使用.showHideTransitionViews
,因此您不必删除视图以防止该视图在过渡中“隐藏”后显示。
在pushButtonPressed(_ :)
的末尾添加以下代码:
if isGraphViewShowing {
counterViewTap(nil)
}
如果用户在显示图形时按下加号按钮,则显示屏将向后摆动以显示计数器。
现在,要使此转换生效,请返回Main.storyboard
并将您的点击手势连接到新添加的counterViewTap(gesture :)
:
构建并运行。 目前,启动应用程序时,您会看到Graph View
。 稍后,您将Graph View
设置为隐藏,这样计数器视图将首先出现。 点按它,您会看到翻转的过渡。
Analyzing the Graph View
image还记得第1部分中的画家模型吗? 它说明您在Core Graphics
中从背面到正面绘制图像。 因此,在编码之前,您需要牢记顺序。 对于Flo
的图形,应为:
- 1)
Gradient background view
- 2)
Clipped gradient under the graph
- 3)
Graph line
- 4)
Circles for the graph points
- 5)
Horizontal graph lines
- 6)
Graph labels
Drawing a Gradient
现在,您将在Graph View
中绘制一个渐变。
打开GraphView.swift
并将代码替换为:
import UIKit
@IBDesignable
class GraphView: UIView {
// 1
@IBInspectable var startColor: UIColor = .red
@IBInspectable var endColor: UIColor = .green
override func draw(_ rect: CGRect) {
// 2
guard let context = UIGraphicsGetCurrentContext() else {
return
}
let colors = [startColor.cgColor, endColor.cgColor]
// 3
let colorSpace = CGColorSpaceCreateDeviceRGB()
// 4
let colorLocations: [CGFloat] = [0.0, 1.0]
// 5
guard let gradient = CGGradient(
colorsSpace: colorSpace,
colors: colors as CFArray,
locations: colorLocations
) else {
return
}
// 6
let startPoint = CGPoint.zero
let endPoint = CGPoint(x: 0, y: bounds.height)
context.drawLinearGradient(
gradient,
start: startPoint,
end: endPoint,
options: []
)
}
}
您需要从上面的代码中了解以下内容:
- 1) 您需要将渐变的开始和结束颜色设置为
@IBInspectable
属性,以便可以在storyboard
中对其进行更改。 - 2)
CG
绘制函数需要知道它们将在其中绘制的上下文,因此您可以使用UIKit
方法UIGraphicsGetCurrentContext()
来获取当前上下文。这就是draw(_ :)
绘制到的对象。 - 3) 所有上下文都有一个色彩空间。这可能是
CMYK
或灰度,但是这里您使用的是RGB
颜色空间。 - 4) 色标描述渐变中的颜色在何处转换。在此示例中,您只有两种颜色,红色变为绿色,但是可能有三个停止点的数组,而红色变为蓝色而变为绿色。停止点在
0
到1
之间,其中0.33
是渐变的三分之一。 - 5) 然后,您需要创建实际的渐变,定义颜色空间,颜色和色标。
- 6) 最后,您需要绘制渐变。
drawLinearGradient(_:start:end:options :)
采用以下参数:- 具有颜色空间,颜色和停止点的
CGGradient
- 起点
- 终点
- 选项标记以扩展渐变
- 具有颜色空间,颜色和停止点的
渐变将填充传递给draw(_ :)
的整个rect
。
打开Main.storyboard
,您会看到渐变出现在Graph View
中。
在storyboard
中,选择Graph View
。 然后在Attributes inspector
中,将Start Color
更改为RGB(250,233,222)
,将End Color
更改为RGB(252,79,8)
。 为此,请单击颜色,然后单击Custom
:
现在进行一些清理工作。 在Main.storyboard
中,依次选择每个视图(主视图除外),然后将Background Color
设置为Clear Color
。 您不再需要黄色,按钮视图也应该具有透明背景。
构建并运行,您会发现该图看起来更好,或者至少它的背景看起来更好。
Clipping Areas
刚才使用渐变时,您会填充视图的整个上下文区域。 但是,如果您不想填充整个区域,则可以创建路径来裁剪绘图区域。
要查看实际效果,请转到GraphView.swift
。
首先,将这些常量添加到GraphView
的顶部,稍后将用它们进行绘制:
private enum Constants {
static let cornerRadiusSize = CGSize(width: 8.0, height: 8.0)
static let margin: CGFloat = 20.0
static let topBorder: CGFloat = 60
static let bottomBorder: CGFloat = 50
static let colorAlpha: CGFloat = 0.3
static let circleDiameter: CGFloat = 5.0
}
在draw(_:)
顶部添加代码:
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: .allCorners,
cornerRadii: Constants.cornerRadiusSize
)
path.addClip()
这将创建一个限制渐变的裁剪区域。 稍后,您将使用相同的技巧在图形线下绘制第二个渐变。
构建并运行,然后查看您的Graph View
具有漂亮的圆角:
注意:使用
Core Graphics
绘制静态视图通常足够快,但是如果视图四处移动或需要频繁重绘,则应使用Core Animation
层。 对Core Animation
进行了优化,以便GPU
(而不是CPU)处理大多数处理。 相比之下,CPU
会在draw(_ :)
中处理由Core Graphics
执行的绘图。如果您使用的是
Core Animation
,则将使用CALayer
的cornerRadius
属性,而不是clipping
。 有关此概念的优质教程,请查看适用于iOS和Swift的自定义控件教程: Custom Control Tutorial for iOS and Swift: A Reusable Knob,您将在其中使用Core Animation
创建自定义控件。
Calculating Graph Points
现在,您需要短暂的绘画时间来制作图形。 您会得到7个点; x轴为“星期几”,y轴为Number of Glasses Drunk
。
首先,设置一周的样本数据。
仍在GraphView.swift
中,在类顶部,添加以下属性:
// Weekly sample data
var graphPoints = [4, 2, 6, 4, 5, 8, 3]
这将保存代表7天的样本数据。
将此代码添加到draw(_ :)
的顶部:
let width = rect.width
let height = rect.height
并将此代码添加到draw(_ :)
的末尾:
// Calculate the x point
let margin = Constants.margin
let graphWidth = width - margin * 2 - 4
let columnXPoint = { (column: Int) -> CGFloat in
// Calculate the gap between points
let spacing = graphWidth / CGFloat(self.graphPoints.count - 1)
return CGFloat(column) * spacing + margin + 2
}
x
轴点由7
个等距点组成。 上面的代码是一个闭包表达式。 可以将其添加为一个函数,但是对于像这样的小型计算,可以使其保持一致。
columnXPoint
将列作为参数,并返回一个值,该值应在x轴上。
添加代码以计算y轴点到draw(_ :)
的末尾:
// Calculate the y point
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
guard let maxValue = graphPoints.max() else {
return
}
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let yPoint = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - yPoint // Flip the graph
}
columnYPoint
也是一个闭包表达式,它以数组中星期几的值作为参数。 它返回y
位置,介于0
和最大数量的喝酒杯数之间。
因为Core Graphics
的原点位于左上角,并且您从左下角的原点绘制图形,所以columnYPoint
会调整其返回值,以使图形的方向符合您的预期。
通过在draw(_ :)
的末尾添加线条绘图代码来继续:
// Draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
// Set up the points line
let graphPath = UIBezierPath()
// Go to start of line
graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
// Add points for each item in the graphPoints array
// at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
graphPath.addLine(to: nextPoint)
}
graphPath.stroke()
在此块中,创建图形的路径。UIBezierPath
是根据graphPoints
中每个元素的x
和y
点构建的。
storyboard
中的Graph View
现在应如下所示:
现在您已经验证了线条绘制正确,将其从draw(_:)
的末尾删除
graphPath.stroke()
只是为了您可以在storyboard
中看到该条线,并验证计算是否正确。
1. Creating the Gradient for the Graph
现在,您将通过使用路径作为剪切路径在该路径下创建渐变。
首先在draw(_ :)
的末尾设置剪切路径:
// Create the clipping path for the graph gradient
// 1 - Save the state of the context (commented out for now)
//context.saveGState()
// 2 - Make a copy of the path
guard let clippingPath = graphPath.copy() as? UIBezierPath else {
return
}
// 3 - Add lines to the copied path to complete the clip area
clippingPath.addLine(to: CGPoint(
x: columnXPoint(graphPoints.count - 1),
y: height))
clippingPath.addLine(to: CGPoint(x: columnXPoint(0), y: height))
clippingPath.close()
// 4 - Add the clipping path to the context
clippingPath.addClip()
// 5 - Check clipping path - Temporary code
UIColor.green.setFill()
let rectPath = UIBezierPath(rect: rect)
rectPath.fill()
// End temporary code
在上面的代码中,您:
- 1) 现在注释掉
context.saveGState()
。 一旦了解了它的作用,您很快就会回到这个问题。 - 2) 将绘制的路径复制到新路径,该路径定义要用渐变填充的区域。
- 3) 用拐角点完成该区域并关闭路径。 这将添加图形的右下角和左下角点。
- 4) 将剪切路径添加到上下文。 当上下文被填充时,实际上仅剪切路径被填充。
- 5) 填充上下文。 请记住,
rect
是传递给draw(_ :)
的上下文区域。
现在,storyboard
中的Graph View
应如下所示:
接下来,您将用从用于背景渐变的颜色创建的渐变替换可爱的绿色。
用以下代码替换注释#5
下的临时代码:
let highestYPoint = columnYPoint(maxValue)
let graphStartPoint = CGPoint(x: margin, y: highestYPoint)
let graphEndPoint = CGPoint(x: margin, y: bounds.height)
context.drawLinearGradient(
gradient,
start: graphStartPoint,
end: graphEndPoint,
options: [])
//context.restoreGState()
在此块中,您找到了酒后酒杯数量最多的地方,并将其用作渐变的起点。
您无法像使用绿色一样填充整个rect
。 渐变将从上下文顶部填充,而不是从图形顶部填充,并且所需的渐变不会显示。
注意注释掉的context.restoreGState()
。 在绘制出绘图点的圆圈后,您将删除注释。
在draw(_ :)
的末尾添加以下内容:
// Draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()
此代码绘制了原始路径。
您的图现在已经真正成形:
2. Drawing the Data Points
在draw(_:)
下面,添加:
// Draw the circles on top of the graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
point.x -= Constants.circleDiameter / 2
point.y -= Constants.circleDiameter / 2
let circle = UIBezierPath(
ovalIn: CGRect(
origin: point,
size: CGSize(
width: Constants.circleDiameter,
height: Constants.circleDiameter)
)
)
circle.fill()
}
在上面的代码中,您通过在计算出的x
和y
点填充数组中每个元素的圆路径来绘制绘图点。
嗯...那些圈子是什么?他们看起来不太圆!
Considering Context States
圆怪异的原因与状态state
有关。图形上下文可以保存状态。因此,当您设置许多上下文属性(例如填充颜色,转换矩阵,颜色空间或剪辑区域)时,实际上是将它们设置为当前图形状态。
您可以使用context.saveGState()
保存状态,该状态将当前图形状态的副本推入状态堆栈(state stack)
。您还可以更改上下文属性,但是当调用context.restoreGState()
时,原始状态从堆栈中移出,并且上下文属性恢复。这就是为什么您看到自己的点很奇怪的原因。
当您仍然在GraphView.swift
中时,在draw(_ :)
中,请先取消注释context.saveGState()
,然后再创建剪切路径。另外,在使用剪切路径之前,请取消注释context.restoreGState()
。
通过这样做,您:
- 1) 使用
context.saveGState()
将原始图形状态压入堆栈。 - 2) 将剪切路径添加到新的图形状态。
- 3) 在剪切路径内绘制渐变。
- 4) 使用
context.restoreGState()
恢复原始图形状态。这是添加剪切路径之前的状态。
您的图形线和圆现在应该更加清晰:
在draw(_ :)
的末尾,添加以下代码以绘制三条水平线:
// Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
// Top line
linePath.move(to: CGPoint(x: margin, y: topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: topBorder))
// Center line
linePath.move(to: CGPoint(x: margin, y: graphHeight / 2 + topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: graphHeight / 2 + topBorder))
// Bottom line
linePath.move(to: CGPoint(x: margin, y: height - bottomBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: height - bottomBorder))
let color = UIColor(white: 1.0, alpha: Constants.colorAlpha)
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
很容易,对吧? 您只是移动到一个点并绘制一条水平线。
Adding the Graph Labels
现在,您将添加标签以使图形更加用户友好。
转到ViewController.swift
并添加以下outlet
属性:
// Label outlets
@IBOutlet weak var averageWaterDrunk: UILabel!
@IBOutlet weak var maxLabel: UILabel!
@IBOutlet weak var stackView: UIStackView!
这增加了用于动态更改平均喝水标签,最大喝水标签以及堆栈视图的日期名称标签的文本的outlets
。
现在转到Main.storyboard
并添加以下视图作为Graph View
的子视图:
前五个子视图是UILabel
。 第四个子视图在图形的顶部旁边右对齐,第五个子视图在图形的底部右侧对齐。 第六个子视图是水平StackView
,其中包含一周中每一天的标签。 您将在代码中更改它们。
按住Shift
键并单击所有标签,然后将字体更改为自定义Avenir Next Condensed Medium style
。
将averageWaterDrunk
,maxLabel
和stackView
连接到Main.storyboard
中的相应视图。 按住Control
键从View Controller
拖动到正确的标签,然后从弹出窗口中选择outlet
:
既然您已经完成了图形视图的设置,请在Main.storyboard
中选择Graph View
并选中Hidden
,这样在应用程序首次运行时该图形就不会出现。
打开ViewController.swift
并添加以下方法来设置标签:
func setupGraphDisplay() {
let maxDayIndex = stackView.arrangedSubviews.count - 1
// 1 - Replace last day with today's actual data
graphView.graphPoints[graphView.graphPoints.count - 1] = counterView.counter
// 2 - Indicate that the graph needs to be redrawn
graphView.setNeedsDisplay()
maxLabel.text = "\(graphView.graphPoints.max() ?? 0)"
// 3 - Calculate average from graphPoints
let average = graphView.graphPoints.reduce(0, +) / graphView.graphPoints.count
averageWaterDrunk.text = "\(average)"
// 4 - Setup date formatter and calendar
let today = Date()
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("EEEEE")
// 5 - Set up the day name labels with correct days
for i in 0...maxDayIndex {
if let date = calendar.date(byAdding: .day, value: -i, to: today),
let label = stackView.arrangedSubviews[maxDayIndex - i] as? UILabel {
label.text = formatter.string(from: date)
}
}
}
这看起来有些笨拙,但是您需要它来设置日历并检索星期几。 为此,您:
- 1) 将今天的数据设置为图形数据数组中的最后一项。
- 2) 重新绘制图形以说明对今天数据的任何更改。
- 3) 使用
Swift
的reduce
来计算一周的平均醉酒量; 这是一种将数组中所有元素求和的非常有用的方法。 - 4) 本节将
DateFormatter
设置为返回每天的第一个字母。 - 5) 此循环遍历
stackView
内的所有标签。 由此,您可以为date formatter
中的每个标签设置文本。
仍在ViewController.swift
中,从counterViewTap(_ :)
调用此新方法。 在条件的else
部分中,注释显示Show graph
,添加以下代码:
setupGraphDisplay()
构建并运行,然后单击计数器。
Mastering the Matrix
您的应用看起来真的很锋利! 不过,您可以通过添加标记来指示要喝的每一杯来改善计数器视图:
现在,您已经对CG
函数进行了一些练习,接下来将使用它们来旋转和平移图形上下文。
请注意,这些标记从中心辐射:
除了绘制上下文外,您还可以选择通过旋转,缩放和转换上下文的转换矩阵来操纵上下文。
乍一看,这似乎令人困惑,但是在完成这些练习之后,它将变得更有意义。 transformations
的顺序很重要,因此这里有一些图表来说明您将要做的事情。
下图是旋转上下文然后在上下文中心绘制一个矩形的结果。
在旋转上下文之前先绘制黑色矩形,然后旋转绿色和红色。 注意两点:
- 1) 上下文在左上角
(0,0)
旋转 - 2) 旋转上下文后,矩形仍显示在上下文的中心。
绘制counter view
的标记时,在旋转上下文之前,请先translate
。
在此图中,矩形标记位于上下文的最左上方。 蓝线概述了translated
后的上下文。 红色虚线表示旋转。 此后,您将再次转换上下文。
将红色矩形绘制到上下文中时,将使它以一定角度出现在视图中。
旋转和平移上下文以绘制红色标记后,需要重置中心,以便可以再次旋转和平移上下文以绘制绿色标记。
就像将上下文状态和剪切路径保存在Graph View
中一样,每次绘制标记时,都将使用转换矩阵保存和恢复状态。
Drawing the Marker
转到CounterView.swift
并将此代码添加到draw(_ :)
的末尾以将标记添加到计数器:
// Counter View markers
guard let context = UIGraphicsGetCurrentContext() else {
return
}
// 1 - Save original state
context.saveGState()
outlineColor.setFill()
let markerWidth: CGFloat = 5.0
let markerSize: CGFloat = 10.0
// 2 - The marker rectangle positioned at the top left
let markerPath = UIBezierPath(rect: CGRect(
x: -markerWidth / 2,
y: 0,
width: markerWidth,
height: markerSize))
// 3 - Move top left of context to the previous center position
context.translateBy(x: rect.width / 2, y: rect.height / 2)
for i in 1...Constants.numberOfGlasses {
// 4 - Save the centered context
context.saveGState()
// 5 - Calculate the rotation angle
let angle = arcLengthPerGlass * CGFloat(i) + startAngle - .pi / 2
// Rotate and translate
context.rotate(by: angle)
context.translateBy(x: 0, y: rect.height / 2 - markerSize)
// 6 - Fill the marker rectangle
markerPath.fill()
// 7 - Restore the centered context for the next rotate
context.restoreGState()
}
// 8 - Restore the original state in case of more painting
context.restoreGState()
在上面的代码中,您:
- 1) 操作上下文的矩阵之前,请保存矩阵的原始状态。
- 2) 定义路径的位置和形状,尽管您尚未绘制它。
- 3) 移动上下文,以便围绕上下文的原始中心进行旋转,如上图中的蓝线所示。
- 4) 保存每个标记的居中上下文状态。
- 5) 使用先前计算的单个角度确定每个标记的角度。 然后旋转并
translate
上下文。 - 6) 在旋转和转换后的上下文的左上方绘制标记矩形。
- 7) 恢复居中上下文的状态。
- 8) 在进行任何旋转或平移之前,请还原上下文的原始状态。
干得不错。 现在,构建并运行并欣赏Flo
精美而内容丰富的UI:
如果您想了解有关自定义布局的更多信息,请考虑以下资源:
- 1) 查阅
Apple
提供的 Quartz 2D Programming Guide。 - 2) 如果您喜欢视频格式,请遵循我们有关Core Graphics的视频课程video course on Core Graphics。
后记
本篇主要讲述了
Gradients
和Contexts
的简单示例,感兴趣的给个赞或者关注~~~