MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift Core Graphics框架探索

2024-02-257.2k 阅读

认识 Core Graphics

Core Graphics 是一个强大的 2D 图形绘制框架,它提供了低级别的 C 语言 API,用于在 iOS、macOS、watchOS 和 tvOS 上进行精确的图形绘制。在 Swift 中使用 Core Graphics,可以充分利用其高性能和灵活性,创建复杂的图形、路径、图像等。Core Graphics 基于 Quartz 图形引擎,该引擎在底层对图形绘制进行了高度优化,这使得开发者能够高效地处理各种图形任务。

上下文与图形状态

在 Core Graphics 中,图形上下文(Graphics Context)是一个核心概念。它保存了绘制操作的相关信息,包括当前的绘图设备(如屏幕、打印机或图像)、颜色空间、变换矩阵等。每个绘制操作都在特定的图形上下文环境中进行。

获取图形上下文

在 Swift 中,获取图形上下文的方式因应用场景而异。例如,在 iOS 的 UIViewdraw(_ rect: CGRect) 方法中,可以通过 UIGraphicsGetCurrentContext() 获取当前的图形上下文:

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    // 在此处进行绘制操作
}

而在 macOS 的 NSViewdraw(_ rect: NSRect) 方法中,可以使用 NSGraphicsContext.current?.cgContext 获取上下文:

override func draw(_ rect: NSRect) {
    guard let context = NSGraphicsContext.current?.cgContext else { return }
    // 绘制操作
}

图形状态栈

图形上下文维护着一个图形状态栈。图形状态包含了诸如线条宽度、颜色、字体等绘图属性。当进行绘制操作时,当前的图形状态决定了绘制的外观。可以使用 saveGState() 方法将当前图形状态压入栈中,并使用 restoreGState() 方法从栈中恢复之前保存的状态。这在需要临时更改绘图属性,然后再恢复到原始状态时非常有用。

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    context.setLineWidth(2.0)
    context.setStrokeColor(UIColor.blue.cgColor)
    context.saveGState()
    context.setLineWidth(4.0)
    context.setStrokeColor(UIColor.red.cgColor)
    // 使用新的属性绘制
    context.restoreGState()
    // 恢复到之前保存的属性继续绘制
}

路径绘制

路径(Path)是 Core Graphics 中用于定义形状的基本元素。路径由一系列直线、曲线和控制点组成。

创建路径

可以使用 CGMutablePath 来创建一个可变路径。例如,要创建一个简单的矩形路径:

let rectPath = CGMutablePath()
rectPath.addRect(CGRect(x: 50, y: 50, width: 100, height: 100))

对于更复杂的形状,可以使用 move(to:) 方法移动到一个起始点,然后使用 addLine(to:) 方法添加直线段,或使用 addCurve(to:control1:control2:) 方法添加曲线段。

let customPath = CGMutablePath()
customPath.move(to: CGPoint(x: 100, y: 100))
customPath.addLine(to: CGPoint(x: 200, y: 100))
customPath.addCurve(to: CGPoint(x: 200, y: 200), control1: CGPoint(x: 200, y: 150), control2: CGPoint(x: 150, y: 200))

绘制路径

获取图形上下文后,可以使用上下文的方法来绘制路径。例如,使用 stroke(_:) 方法绘制路径的轮廓,使用 fill(_:) 方法填充路径内部。

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    let rectPath = CGMutablePath()
    rectPath.addRect(CGRect(x: 50, y: 50, width: 100, height: 100))
    context.setStrokeColor(UIColor.blue.cgColor)
    context.setLineWidth(2.0)
    context.stroke(rectPath)
    context.setFillColor(UIColor.green.cgColor)
    context.fill(rectPath)
}

颜色与渐变

颜色在图形绘制中起着至关重要的作用,Core Graphics 提供了丰富的颜色处理功能,同时也支持渐变效果。

颜色设置

在 Swift 中,可以使用 CGColor 来表示颜色。UIColorNSColor 都有对应的 cgColor 属性,可以方便地转换为 CGColor。例如,设置填充颜色为红色:

let redColor = UIColor.red.cgColor
context.setFillColor(redColor)

还可以通过 CGColorSpaceCGColorCreate 等函数创建自定义颜色空间和颜色。例如,创建一个灰度颜色:

let grayColorSpace = CGColorSpaceCreateDeviceGray()
let components: [CGFloat] = [0.5, 1.0] // 灰度值和透明度
let grayColor = CGColor(colorSpace: grayColorSpace, components: components)!
context.setFillColor(grayColor)

渐变

Core Graphics 支持线性渐变(Linear Gradient)和径向渐变(Radial Gradient)。要创建线性渐变,需要定义渐变的起始点、结束点以及渐变的颜色数组。

let startPoint = CGPoint(x: 0, y: 0)
let endPoint = CGPoint(x: 200, y: 0)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let colors: [CGColor] = [UIColor.red.cgColor, UIColor.blue.cgColor]
let colorLocations: [CGFloat] = [0.0, 1.0]
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: colorLocations)!
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])

对于径向渐变,需要定义起始圆心、半径,结束圆心、半径以及颜色数组等参数。

let startCenter = CGPoint(x: 100, y: 100)
let startRadius: CGFloat = 0
let endCenter = CGPoint(x: 100, y: 100)
let endRadius: CGFloat = 100
let radialGradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: colorLocations)!
context.drawRadialGradient(radialGradient, startCenter: startCenter, startRadius: startRadius, endCenter: endCenter, endRadius: endRadius, options: [])

图像绘制

Core Graphics 允许将图像绘制到图形上下文中,支持多种图像格式。

加载图像

在 iOS 中,可以使用 UIImage 类加载图像,然后获取其 CGImage 属性。

if let image = UIImage(named: "exampleImage") {
    if let cgImage = image.cgImage {
        // 可以在此处使用 cgImage 进行绘制
    }
}

在 macOS 中,使用 NSImage 类加载图像,并通过 cgImage(forProposedRect:context:hints:) 方法获取 CGImage

if let image = NSImage(named: NSImage.Name("exampleImage")) {
    if let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
        // 绘制操作
    }
}

绘制图像

获取 CGImage 后,可以使用图形上下文的 draw(_:in:) 方法将图像绘制到指定的矩形区域。

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    if let image = UIImage(named: "exampleImage") {
        if let cgImage = image.cgImage {
            let drawRect = CGRect(x: 50, y: 50, width: 200, height: 200)
            context.draw(cgImage, in: drawRect)
        }
    }
}

变换操作

Core Graphics 提供了丰富的变换操作,包括平移、旋转和缩放,这些操作可以通过变换矩阵来实现。

平移变换

平移变换通过 translateBy(x:y:) 方法实现,它将图形上下文的原点移动到指定的位置。例如,将图形上下文向右平移 100 个单位,向下平移 50 个单位:

context.translateBy(x: 100, y: 50)
// 在此之后的绘制操作将基于新的原点位置

旋转变换

旋转变换使用 rotate(by:) 方法,参数是旋转的弧度。例如,将图形上下文顺时针旋转 45 度(即 π/4 弧度):

let angle = CGFloat.pi / 4
context.rotate(by: angle)
// 绘制操作将以旋转后的角度进行

缩放变换

缩放变换通过 scaleBy(x:y:) 方法实现,分别指定在 x 和 y 方向上的缩放因子。例如,将图形上下文在 x 和 y 方向上都放大 2 倍:

context.scaleBy(x: 2, y: 2)
// 绘制的图形将以放大后的比例显示

组合变换

可以组合多个变换操作,例如先平移,再旋转,最后缩放:

context.translateBy(x: 100, y: 100)
context.rotate(by: CGFloat.pi / 4)
context.scaleBy(x: 0.5, y: 0.5)
// 进行绘制操作,将应用所有组合的变换

文本绘制

在 Core Graphics 中也可以进行文本绘制,虽然 UIKit 和 AppKit 提供了更高级的文本绘制方法,但 Core Graphics 提供了更底层的控制。

设置字体

首先需要选择一种字体,可以通过 CTFontCreateWithName 函数创建 CTFont 对象。例如,创建一个 16 号的系统字体:

let fontName = CTFontCreateUIFontNameForLanguage(kCTFontSystemFontType, nil)
let font = CTFontCreateWithName(fontName, 16, nil)

绘制文本

将文本绘制到图形上下文中,需要先将文本转换为 CFAttributedString,并设置字体等属性。然后使用 CTLineCreateWithAttributedString 创建 CTLine 对象,最后使用 CTLineDraw 方法绘制文本。

let text = "Hello, Core Graphics!" as CFString
let attributes: [NSAttributedString.Key: Any] = [
   .font: font!
]
let attributedString = CFAttributedStringCreate(nil, text, attributes as CFDictionary)
let line = CTLineCreateWithAttributedString(attributedString)
let textRect = CGRect(x: 100, y: 100, width: 200, height: 50)
context.textMatrix = CGAffineTransform.identity
context.textPosition = CGPoint(x: textRect.minX, y: textRect.minY)
CTLineDraw(line, context)

Core Graphics 与动画

虽然 Core Graphics 本身没有直接提供动画支持,但可以结合 UIKit 或 AppKit 的动画机制来实现动画效果。例如,在 iOS 中,可以通过 UIView.animate 方法来动画化图形上下文的变换或路径的更改。

UIView.animate(withDuration: 2.0) {
    self.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
    // 假设 self 是一个 UIView,这里通过变换实现缩放动画
    // 也可以在动画块中更改路径等其他绘制元素
}

在 macOS 中,可以使用 NSAnimation 类及其相关子类来实现类似的动画效果。例如,使用 NSViewAnimation 来动画化视图的大小变化,而视图内部的绘制可以基于 Core Graphics。

Core Graphics 的性能优化

在使用 Core Graphics 进行复杂图形绘制时,性能优化至关重要。

减少不必要的绘制

尽量避免在每次视图更新时都进行完整的绘制。可以使用脏矩形(Dirty Rectangles)概念,只绘制发生变化的区域。在 iOS 中,UIViewsetNeedsDisplay(_ rect: CGRect?) 方法可以指定需要重绘的矩形区域。

let changedRect = CGRect(x: 50, y: 50, width: 100, height: 100)
self.setNeedsDisplay(changedRect)

缓存绘制结果

对于一些静态或不经常变化的图形,可以将绘制结果缓存起来。例如,将一个复杂路径的绘制结果缓存为图像,然后在每次需要绘制时直接绘制该图像,而不是重新计算路径和绘制。

var cachedImage: UIImage?
if let cached = cachedImage {
    context.draw(cached.cgImage!, in: drawRect)
} else {
    // 绘制复杂路径
    let complexPath = CGMutablePath()
    // 构建复杂路径
    context.addPath(complexPath)
    context.drawPath(using:.fillStroke)
    // 缓存绘制结果
    UIGraphicsBeginImageContextWithOptions(drawRect.size, false, 0)
    guard let context = UIGraphicsGetCurrentContext() else { return }
    context.draw(complexPath, in: drawRect)
    cachedImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

优化路径操作

在创建路径时,尽量减少路径的节点数量。复杂的路径会增加绘制的计算量。例如,对于曲线,可以使用更合适的控制点来减少曲线的段数,同时确保曲线的形状符合需求。

通过合理运用 Core Graphics 的各种功能,并结合性能优化技巧,开发者可以在 Swift 应用中创建出高性能、精美的 2D 图形界面。无论是简单的图形绘制,还是复杂的动画和交互效果,Core Graphics 都为开发者提供了强大的底层支持。从基本的路径绘制到复杂的变换、文本绘制以及与动画的结合,Core Graphics 框架在 Swift 开发中有着广泛的应用场景,为开发者提供了无限的创作可能。