CoreGraphic框架解析 (二十六) —— 以高效的方式绘制图案(一)

版本记录

版本号 时间
V1.0 2021.05.30 星期日

前言

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 (二)
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(二)
22. CoreGraphic框架解析 (二十二) —— Gradients 和 Contexts的简单示例(一)
23. CoreGraphic框架解析 (二十三) —— Gradients 和 Contexts的简单示例(二)
24. CoreGraphic框架解析 (二十四) —— 基于Core Graphic的重复图案的绘制(一)
25. CoreGraphic框架解析 (二十五) —— 基于Core Graphic的重复图案的绘制(二)

开始

首先看下主要内容:

了解如何使用Core Graphics以高效的方式绘制图案。内容来自翻译

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就是正文啦。

Core Graphics是一组强大而友好的 API,用于在 UIKit应用程序中进行绘图。 除了形状和渐变等基元之外,您还可以使用 Core Graphics编写图案。 Core Graphics Patterns是一组任意的图形操作集,可以平铺以填充一个区域。 您可以使用重复的形状为您的应用程序创建华丽的背景。 Core Graphics Patterns是一种缩放绘图以填充屏幕上任何形状的高效方法。

在本教程中,您将学习如何使用 Core Graphics 执行以下操作:

  • 创建基于路径的绘图。
  • 画一个图案(pattern)
  • 变换图案。
  • 使用模式完成一个名为 Recall 的模式识别游戏。

注意:如果您是 Core Graphics 的新手,最好查看我们关于该主题的一些入门级教程。 考虑学习 Lines, Rectangles, and Gradients以及Arcs and Paths教程,以更好地理解您将在此处构建的基础。

打开入门应用程序。 您会看到以下内容(颜色和字母可能会有所不同):

RecallLeft vs Right 大脑训练应用程序中的游戏中获得灵感。 游戏的目标是为视野中的物体选择最受欢迎的方向。 一旦您做出选择,就会显示一组新的对象。 在游戏结束前您有五次尝试机会。

Recall 在四个象限中对模式对象进行分组。 入门应用程序中的每个象限都有一个标签。 文本代表方向,背景颜色代表填充颜色。

作为一个起点,游戏相当平庸。

你的任务是使用 Core Graphics 模式将这个悲伤的应用程序变成下面的完成的应用程序:

Xcode 中查看项目。这些是主要文件:

  • GameViewController.swift:控制游戏玩法并显示游戏视图。
  • ResultViewController.swift:显示最终得分和重新开始游戏的按钮。
  • PatternView.swift:显示游戏视图中象限之一的模式视图。

在本教程即将结束时,您将增强 PatternView 以显示所需的图案。

首先,您将在 Playground 中对新的和改进的 PatternView 进行原型设计。这使您可以在学习 Core Graphics 模式的来龙去脉的同时更快地进行迭代。完成后,您将相关代码转移到 Recall启动项目。

Xcode 中,转到File ▸ New ▸ Playground….。选择Single View模板。单击 Next,将 Playground 命名为 PatternView.playground,将其添加到 Recall 工作区和 Recall 文件夹组,然后单击 Create。您的 Playground 包含一个带有单个视图的视图控制器。

注意:如果 Xcode 在尝试加载新程序时出现错误,那么古老的“重新启动 Xcode”应该会让您继续前进。

选择 Editor ▸ Run Playground来执行 Playground。单击Show the Assistant Editor以显示您的入门视图。它显示标志性的“Hello World!”结果:

在您阅读下一部分时,您将用您的模式视图替换初始视图。 是时候开始了!


Understanding the Anatomy of a Pattern

在我们之前的previous Core Graphics tutorials教程中,您已经看到了如何定义和绘制路径:

路径是一组描述形状的指令。 上面的示例显示您用黑线描边路径。 右边的路径用黑色描边并用橙色填充。

使用 Core Graphics,您还可以使用图案(pattern)描边或填充路径。 下面的示例显示了填充路径的彩色图案:

1. Setting up the Pattern

您可以通过执行以下操作来设置模式:

  • 1) 编写一个绘制单个图案单元格的方法。
  • 2) 使用包括如何绘制和放置单个单元格的参数创建图案。
  • 3) 定义您的图案将使用的颜色信息。
  • 4) 使用您创建的图案绘制所需的路径。

现在,查看带有额外填充的略有不同的图案单元格。 黑色细边框显示单元格的边界:

您将编写一个在单元格cell边界内进行绘制的 draw方法。 Core Graphics 剪裁在单元格边界之外绘制的任何内容。 Core Graphics 还希望您每次都以完全相同的方式绘制图案单元格。

在设置图案单元格时,您的绘制方法可以应用颜色。 这是一个彩色图案。 未着色或遮罩图案是在draw方法之外应用填充颜色的图案。 这使您可以灵活地在有意义的地方设置图案颜色。

Core Graphics 反复调用你的 draw 方法来设置你的图案。 图案创建参数定义了图案的外观。 下面的示例显示了一个基本的重复图案,其中单元格彼此相邻排列:

您可以在配置图案时指定图案单元格之间的间距:

您还可以应用变换来更改图案的外观。 下图显示了在由模糊边框表示的空间内绘制的图案:

第一个显示未更改的图案。 在第二个中,您会看到一个translated的模式。 第三个显示旋转的图案。 同样,图案单元格周围的黑色边框突出显示其边界。

配置模式时,您有很多可用的选项。 您将在下一部分开始将所有这些放在一起。

2. Creating the Pattern View

PatternView.playground 中的视图控制器类之前添加以下代码:

class PatternView: UIView {
  override func draw(_ rect: CGRect) {
    // 1
    guard let context = UIGraphicsGetCurrentContext()
    else { return }
    // 2
    UIColor.orange.setFill()
    // 3
    context.fill(rect)
  }
}

这代表您的图案的自定义视图。 在这里,您重写 draw(_:)以执行以下操作:

  • 1) 获取视图的图形上下文。
  • 2) 设置上下文的当前填充颜色。
  • 3) 使用当前的填充颜色填充整个上下文。

将图形上下文视为您可以在其上绘制的画布。 上下文包含诸如将填充或描边路径的颜色等信息。 在使用上下文的颜色信息绘制路径之前,您可以在画布中绘制路径。

MyViewController 中,将 loadView() 中与 label相关的代码替换为以下代码:

let patternView = PatternView()
patternView.frame = CGRect(x: 10, y: 10, width: 200, height: 200)
view.addSubview(patternView)

这将创建图案视图的实例,设置frame并将其添加到视图view中。

Shift-Command-Return 运行 Playground。 之前的label不见了,取而代之的是一个橙色的子视图:

着色只是旅程的开始。 你知道还有更多的来源!

3. Drawing a Black Circle in the Pattern Cell

PatternView的顶部内添加以下属性:

let drawPattern: CGPatternDrawPatternCallback = { _, context in
  context.addArc(
    center: CGPoint(x: 20, y: 20),
    radius: 10.0,
    startAngle: 0,
    endAngle: 2.0 * .pi,
    clockwise: false)
  context.setFillColor(UIColor.black.cgColor)
  context.fillPath()
}

上面的代码在图形上下文中绘制了一个圆形路径并用黑色填充它。

这表示您图案单元格的绘制方法,它是 CGPatternDrawPatternCallback 类型。 这是一个带有两个参数的闭包:

  • 1) 指向与图案关联的私有数据的指针。 你没有使用私有数据,所以你在这里使用了一个未命名的参数。
  • 2) 用于绘制图案单元格的图形context

将以下代码添加到 draw(_:)的末尾:

var callbacks = CGPatternCallbacks(
  version: 0, 
  drawPattern: drawPattern, 
  releaseInfo: nil)

CGPatternCallbacks 是一种用于保存用于绘制图案的回调的结构。 有两种类型的回调 - 一种用于图案,您已经创建,另一种用于清理和释放您没有使用的任何私有数据。 如果您在图案中使用私有数据,您通常会设置一个release回调。 由于您没有在 draw 方法中使用私有数据,因此您为此回调传递 nil

4. Creating the Pattern

要创建图案,请在上面的代码之后添加以下内容:

guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
  matrix: .identity,
  xStep: 50,
  yStep: 50,
  tiling: .constantSpacing,
  isColored: true,
  callbacks: &callbacks)
else { return }

这将创建一个图案(pattern)对象。 在上面的代码中,您传入以下参数:

  • info:指向您要在图案回调中使用的任何私有数据的指针。 你在这里传入 nil 因为你没有使用任何。
  • bounds:图案单元格的边界框。
  • matrix:表示要应用的变换的矩阵。 您传入单位矩阵,因为您没有应用任何变换。
  • xStep:图案单元格之间的水平间距。
  • yStep:图案单元格之间的垂直间距。
  • tilingCore Graphics 应该使用的技术来解决用户空间单位和设备像素之间的差异。
  • isColored:图案单元格绘制方法是否应用颜色。 您将其设置为 true,因为您的 draw 方法设置了一种颜色。
  • callbacks:指向保存图案回调的结构的指针。

在图案分配后立即添加以下代码:

var alpha: CGFloat = 1.0
context.setFillPattern(pattern, colorComponents: &alpha)
context.fill(rect)

上面的代码为图形上下文设置填充图案。 对于彩色图案,还必须传递一个alpha值以指定图案的不透明度。 图案绘制方法提供颜色。 最后,代码用图案绘制视图的frame区域。

Shift-Command-Return 运行 Playground。 你的图案没有出现。 奇怪的。 这是怎么回事?

5. Setting the Pattern’s Color Space

您需要向 Core Graphics 提供有关图案颜色空间的信息,以便它知道如何处理图案颜色。 色彩空间指定如何解释要显示的色彩值。

alpha 声明之前添加以下内容:

// 1
guard let patternSpace = CGColorSpace(patternBaseSpace: nil)
else { return }
// 2
context.setFillColorSpace(patternSpace)

下面是代码的作用:

  • 1) 创建图案色彩空间。 对于彩色图案,基本空间参数应该为nil。 这将着色委托给您的图案单元格绘制方法。
  • 2) 将填充颜色空间设置为您定义的图案颜色空间。

playground。 是的! 现在,您应该看到一个圆形的黑色图案:

接下来,您将了解有关配置图案的更多信息。

6. Configuring the Pattern

在您的 Playground 中,更改设置pattern的间距参数如下:

xStep: 30,
yStep: 30,

playground。 请注意,圆点似乎彼此更接近:

这是有道理的,因为您缩小了图案单元之间的步长。

现在,更改间距参数如下:

xStep: 20,
yStep: 20,

运行playground。 你的圈子变成了四分之一:

7. Changing the Centers of the Circles

要了解原因,请注意您的 draw 方法返回一个以 (20, 20)为中心、半径为 10 的圆。 图案的水平和垂直位移为 20。单元格的边界框在原点(0,0)处为 20×20。 这导致从右下边缘开始的重复四分之一圆。

将绘制圆的 (context.addArc)drawPattern更改为以下内容:

context.addArc(
  center: CGPoint(x: 10, y: 10), 
  radius: 10.0,
  startAngle: 0, 
  endAngle: 2.0 * .pi,
  clockwise: false)

您已将中心点更改为(10, 10)而不是(20, 20)

运行playground。 由于圆心的偏移,你又回到了整个圆:

图案单元格边界也与圆完美匹配,导致每个单元格与其他单元格相邻。

8. Applying a Transformation to the Pattern

您可以通过许多有趣的方式转换您的图案。 在 draw(_:)中,将 pattern 替换为以下内容:

// 1
let transform = CGAffineTransform(translationX: 5, y: 5)
// 2
guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
  matrix: transform,
  xStep: 20,
  yStep: 20,
  tiling: .constantSpacing,
  isColored: true,
  callbacks: &callbacks)
else { return }

您已经通过向它传递一个转换矩阵来修改图案。 这是一个仔细的说明:

  • 1) 创建表示平移的仿射变换矩阵(affine transformation matrix)
  • 2) 通过在矩阵(matrix)中传递它来配置图案以使用此转换。

运行playground。 请注意图案如何向右和向下移动以匹配您定义的翻译:

除了变换您的图案,您还可以缩放和旋转图案单元格。 稍后在为游戏应用程序构建图案时,您将看到如何旋转图案。

9. Adding Fill and Stroke to the Pattern

这是填充和描边彩色图案的方法。 在 drawPattern 中,替换设置填充颜色的行并使用以下内容填充路径:

context.setFillColor(UIColor.yellow.cgColor)
context.setStrokeColor(UIColor.darkGray.cgColor)
context.drawPath(using: .fillStroke)

在这里,您将填充颜色更改为黄色并设置描边颜色。 然后使用填充和描边路径的选项调用 drawPath(using:)

运行您的playground并检查图案现在是否显示了您的新填充颜色和描边:

到目前为止,您已经使用了彩色图案,并在图案绘制方法中定义了颜色。 在完成的游戏中,您必须创建具有不同颜色的图案。 你可能意识到为每种颜色编写一个绘制方法并不是要走的路。 这就是masking patterns发挥作用的地方。


Using Masking Patterns

Masking patterns在图案单元格绘制方法之外定义其颜色信息。 这允许您更改图案颜色以满足您的需要。

下面是一个没有颜色关联的masking pattern的例子:

图案到位后,您现在可以应用颜色。 下面的第一个示例显示应用于mask的蓝色,第二个示例显示橙色:

现在,您要将一直使用的图案更改为masking pattern

drawPattern 中删除设置填充和描边颜色的代码并绘制路径并将其替换为以下内容:

context.fillPath()

这会将代码恢复为填充路径。

pattern替换为以下内容:

guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
  matrix: transform,
  xStep: 25,
  yStep: 25,
  tiling: .constantSpacing,
  isColored: false,
  callbacks: &callbacks)
else { return }

这将isColored 设置为 false,将您的图案更改为masking pattern。 您还已将垂直和水平间距增加到25。现在,您需要提供图案的色彩空间信息。

将现有的 patternSpace 分配替换为以下内容:

let baseSpace = CGColorSpaceCreateDeviceRGB()
guard let patternSpace = CGColorSpace(patternBaseSpace: baseSpace)
else { return }

在这里,您可以获得对标准设备相关 RGB 颜色空间的引用。 然后您将图案颜色空间更改为此值而不是之前的 nil 值。

在其下方,替换您创建 alpha 的行并使用以下内容设置上下文填充模式:

let fillColor: [CGFloat] = [0.0, 1.0, 1.0, 1.0]
context.setFillPattern(pattern, colorComponents: fillColor)

这会在填充图案时创建应用于蒙版下方的颜色。

运行playground。 您的图案颜色会更新以反映您在 draw 方法之外配置的青色设置:

1. Stroking and Filling the Masking Pattern

现在,是时候描边和填充遮罩图案了。 这就像描边彩色图案。

drawPattern 中的 context.fillPath()行替换为以下内容:

context.setStrokeColor(UIColor.darkGray.cgColor)
context.drawPath(using: .fillStroke)

尽管您在 draw(_:)内设置了描边颜色,但您的图案颜色仍然在方法外设置。

运行 Playground 以查看描边图案:

您现在已经积累了使用不同图案配置和遮罩图案的经验。 您可以开始构建Recall所需的图案。


Creating the Game Pattern

将以下代码添加到 Playground 的顶部:

extension CGPath {
  // 1
  static func triangle(in rect: CGRect) -> CGPath {
    let path = CGMutablePath()
    // 2
    let top = CGPoint(x: rect.width / 2, y: 0)
    let bottomLeft = CGPoint(x: 0, y: rect.height)
    let bottomRight = CGPoint(x: rect.width, y: rect.height)
    // 3
    path.addLines(between: [top, bottomLeft, bottomRight])
    // 4
    path.closeSubpath()
    return path
  }
}

通过代码,一步一步:

  • 1) 扩展 CGPath 以创建三角形路径。
  • 2) 指定构成三角形的三个点。
  • 3) 在点之间添加线。
  • 4) 关闭路径。

然后在 PatternView 中,添加以下空枚举:

enum Constants {
  static let patternSize: CGFloat = 30.0
  static let patternRepeatCount: CGFloat = 2
}

这些代表您在设置图案时将使用的常量。 patternSize 定义模式单元格大小, patternRepeatCount定义模式视图中模式单元格的数量。

1. Drawing a Triangle

Constants 定义之后添加以下内容:

let drawTriangle: CGPatternDrawPatternCallback = { _, context in
  let trianglePath = CGPath.triangle(in:
    CGRect(
      x: 0,
      y: 0,
      width: Constants.patternSize,
      height: Constants.patternSize))
  context.addPath(trianglePath)
  context.fillPath()
}

这定义了用于绘制三角形图案的新回调。 在其中,您调用CGPath.triangle(in :)返回表示三角形的路径。 然后在填充之前将此路径添加到上下文中。

请注意,闭包没有指定填充颜色,因此它可以是一个遮罩图案。

draw(_:)中,将回调callbacks更改为以下内容:

var callbacks = CGPatternCallbacks(
  version: 0, 
  drawPattern: drawTriangle, 
  releaseInfo: nil)

您现在正在使用三角形绘制callback

2. Drawing Repeating Triangles as a Pattern

删除 drawPattern,因为它不再需要了。

另外,在draw(_ :)中,将分配transformpattern的代码替换为以下代码:

// 1
let patternStepX = rect.width / Constants.patternRepeatCount
let patternStepY = rect.height / Constants.patternRepeatCount
// 2
let patternOffsetX = (patternStepX - Constants.patternSize) / 2.0
let patternOffsetY = (patternStepY - Constants.patternSize) / 2.0
// 3
let transform = CGAffineTransform(
  translationX: patternOffsetX,
  y: patternOffsetY)
// 4
guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(
    x: 0,
    y: 0,
    width: Constants.patternSize,
    height: Constants.patternSize),
  matrix: transform,
  xStep: patternStepX,
  yStep: patternStepY,
  tiling: .constantSpacing,
  isColored: false,
  callbacks: &callbacks)
else { return }

这是该代码的作用,一步一步:

  • 1) 使用视图的宽度和高度以及视图中的模式单元数来计算水平和垂直步长。
  • 2) 计算尺寸以在其边界内水平和垂直居中模式单元格。
  • 3) 根据您定义的居中变量设置CGAffineTransform 转换。
  • 4) 根据您计算出的参数创建图案对象。

运行playground。 您将看到四个三角形,每个三角形都在其边界内垂直和水平居中:

接下来,您将获得与 Recall 应用程序更加匹配的背景颜色。

MyViewController中,如下更改loadView()中的背景色设置:

view.backgroundColor = .lightGray

接下来,转到 PatternView 并更改 draw(_:)中的上下文填充设置,如下所示:

UIColor.white.setFill()

运行playground。 你的主视图的背景现在应该是灰色的,图案视图的背景是白色的:

3. Customizing the Pattern View

现在您已正确显示基本图案,您可以进行更改以控制图案方向。

Constants之后的 PatternView 顶部附近添加以下枚举:

enum PatternDirection: CaseIterable {
  case left
  case top
  case right
  case bottom
}

这表示三角形可以指向的不同方向。 它们与您的入门应用程序中的方向相匹配。

将以下属性添加到 PatternView

var fillColor: [CGFloat] = [1.0, 0.0, 0.0, 1.0]
var direction: PatternDirection = .top

这表示您将应用于遮罩图案和图案方向的颜色。 该类设置默认颜色为红色(四个数组分量代表红色、绿色、蓝色和alpha)和默认方向为顶部。

删除在 draw(_:) 底部附近找到的局部 fillColor 声明。 这将确保您改用实例属性。

transform替换为以下内容:

// 1
var transform: CGAffineTransform
// 2
switch direction {
case .top:
  transform = .identity
case .right:
  transform = CGAffineTransform(rotationAngle: 0.5 * .pi)
case .bottom:
  transform = CGAffineTransform(rotationAngle: .pi)
case .left:
  transform = CGAffineTransform(rotationAngle: 1.5 * .pi)
}
// 3
transform = transform.translatedBy(x: patternOffsetX, y: patternOffsetY)

这是刚刚发生的事情:

  • 1) 为您的pattern transform声明一个 CGAffineTransform变量。
  • 2) 如果图案方向是top,则将变换分配为单位矩阵。 否则,变换是基于方向的旋转。 例如,如果图案指向右侧,则旋转为 π / 2 弧度或顺时针 90º
  • 3) 应用 CGAffineTransform 转换以在其边界内居中pattern cell

运行playground。 根据您的默认图案填充颜色,您的三角形是红色的:

4. Changing the Pattern’s Color and Direction

现在是设置代码来控制和测试图案颜色和方向的好时机。

在属性定义后的PatternView中添加以下方法:

init(fillColor: [CGFloat], direction: PatternDirection = .top) {
  self.fillColor = fillColor
  self.direction = direction
  super.init(frame: CGRect.zero)
}
  
required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
}

这将设置一个初始化器,该初始化器采用填充颜色和图案方向。 direction有一个默认值。

您还为storyboard初始化视图添加了所需的初始化程序。 稍后当您将代码传输到应用程序时,您将需要它。

MyViewController 中,更改patternView,因为您更改了初始化器:

let patternView = PatternView(
  fillColor: [0.0, 1.0, 0.0, 1.0],
  direction: .right)

在这里,您使用非默认值实例化模式视图。

运行playground。 您的三角形现在是绿色并指向右侧:

恭喜! 你已经使用 Playgrounds 完成了图案的原型设计。 是时候在 Recall 中使用这种模式了。

5. Updating the Game to Use the Pattern

打开 Recall 启动项目。 转到 PatternView.swift 并将 CGPath 扩展名从您的playground复制到文件末尾。

接下来,将 PatternView.swift 中的 PatternView 替换为您的 Playground 中的类。

注意:您可以使用 Xcode 的代码折叠功能极大地简化此过程。 在 Playground 中,将光标放在class PatternView: UIView {的左大括号之后,然后从菜单中选择 Editor ▸ Code Folding ▸ Fold。 三击生成的折叠线以选择整个类,然后按 Command-C。 在项目中,重复折叠和选择类的过程。 按 Command-V 替换它。

构建并运行应用程序。 您应该会看到类似以下内容的内容:

有些事情不太对劲。 您的图案似乎卡在默认模式下。 看起来新的游戏视图没有刷新图案视图。

转到 GameViewController.swift 并将以下内容添加到 setupPatternView(_:towards:sharingColor:)的末尾:

patternView.setNeedsDisplay()

这会提示系统重新绘制图案,以便它选取新的图案信息。

构建并运行应用程序。 您现在应该看到颜色和方向很好地混合在一起:

点击其中一个答案按钮并玩游戏以检查一切是否按预期进行。

恭喜您完成Recall! 从简单的油漆工作令人麻木的日子里,您已经走了很长一段路。


Considering Performance When Using Patterns

Core Graphics图案真的很快。您可以使用以下几个选项来绘制图案:

  • 1) 使用您在本教程中使用的 Core Graphics pattern APIs
  • 2) 使用 UIKit 包装方法,例如 UIColor(patternImage:)
  • 3) 在具有许多 Core Graphics 调用的循环中绘制所需的图案。

如果您的模式只绘制一次,则 UIKit 包装器方法是最简单的。它的性能也应该与较低级别的 Core Graphics 调用相媲美。何时使用它的一个示例是绘制静态背景图案。

与运行在主线程上的 UIKit 不同,Core Graphics 可以在后台线程中工作。 Core Graphics 图案在处理复杂的绘图或动态图案时性能更高。

在循环中绘制图案是最慢的选择。Core Graphics pattern进行一次绘制调用并缓存结果,使其更高效。

您现在应该对如何使用 Core Graphics 来创建图案有一个深入的了解。查看 Patterns and Playgrounds教程,了解如何使用 UIKit构建模式。

您可能还想通读Curves and Layers教程,该教程侧重于绘制各种曲线并使用layer克隆它们。

后记

本篇主要讲述了以高效的方式绘制图案,感兴趣的给个赞或者关注~~~

推荐阅读更多精彩内容