Swift Core Graphics框架探索
认识 Core Graphics
Core Graphics 是一个强大的 2D 图形绘制框架,它提供了低级别的 C 语言 API,用于在 iOS、macOS、watchOS 和 tvOS 上进行精确的图形绘制。在 Swift 中使用 Core Graphics,可以充分利用其高性能和灵活性,创建复杂的图形、路径、图像等。Core Graphics 基于 Quartz 图形引擎,该引擎在底层对图形绘制进行了高度优化,这使得开发者能够高效地处理各种图形任务。
上下文与图形状态
在 Core Graphics 中,图形上下文(Graphics Context)是一个核心概念。它保存了绘制操作的相关信息,包括当前的绘图设备(如屏幕、打印机或图像)、颜色空间、变换矩阵等。每个绘制操作都在特定的图形上下文环境中进行。
获取图形上下文
在 Swift 中,获取图形上下文的方式因应用场景而异。例如,在 iOS 的 UIView
的 draw(_ rect: CGRect)
方法中,可以通过 UIGraphicsGetCurrentContext()
获取当前的图形上下文:
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// 在此处进行绘制操作
}
而在 macOS 的 NSView
的 draw(_ 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
来表示颜色。UIColor
和 NSColor
都有对应的 cgColor
属性,可以方便地转换为 CGColor
。例如,设置填充颜色为红色:
let redColor = UIColor.red.cgColor
context.setFillColor(redColor)
还可以通过 CGColorSpace
和 CGColorCreate
等函数创建自定义颜色空间和颜色。例如,创建一个灰度颜色:
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 中,UIView
的 setNeedsDisplay(_ 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 开发中有着广泛的应用场景,为开发者提供了无限的创作可能。