NSAffineTransform — 是来自 AppKit(macOS)的一个类,用于表示二维图形中的仿射变换。它封装了一个 3×3 的矩阵,该矩阵描述了座标的平移、缩放、旋转和剪切的组合。根据 Apple Documentation (2026),NSAffineTransform 在 Core Graphics 中广泛用于在 macOS 上变换路径、点和图形上下文。
主要内容
NSAffineTransform — 是来自 AppKit 框架的一个类,仅在 macOS 上可用。它通过一个 3×3 的矩阵来表示二维空间中的仿射变换。与来自 Core Graphics 的 CGAffineTransform(在 iOS 上可用)不同,NSAffineTransform 是 CGAffineTransform 结构的面向对象封装,添加了方便的方法来处理 NSBezierPath、NSPoint 和 NSSize。
仿射变换 — 是平面的一种变换,它保持“点在直线上”的关系和线条的平行性。换句话说,经过仿射变换后,平行线保持平行,直线保持直线。然而,距离、角度和面积可能会改变:缩放、旋转和剪切是仿射变换的特殊情况。
在 iOS 上,NSAffineTransform 的对应物是来自 Core Graphics 的 CGAffineTransform 结构。两个 API 都基于相同的数学模型 — 一个 3×3 的仿射变换矩阵,其中最后一行始终为 [0, 0, 1]。区别在于,NSAffineTransform 是一个类(在 Objective-C 中具有 retain/release),而 CGAffineTransform 是 Swift 和 C 中的结构(值类型)。
NSAffineTransform 支持四种基本类型的变换:translate(平移)、scale(缩放)、rotate(旋转)和 shear(剪切)。每种类型由一个独立的方法表示,可以应用于现有的变换对象,也可以用于创建新的变换。
| 操作 | NSAffineTransform 方法 | 参数 | 视觉效果 |
|---|---|---|---|
| Translate | translateXBy:yBy: | deltaX, deltaY | 沿 X 和 Y 轴移动对象 |
| Scale | scaleBy: / scaleXBy:yBy: | factor / scaleX, scaleY | 放大或缩小尺寸 |
| Rotate | rotateByDegrees: / rotateByRadians: | 以度或弧度为单位的角度 | 绕坐标原点旋转 |
| Shear | 通过 setTransformStruct | 矩阵的 m12、m21 分量 | 沿轴剪切对象 |
Translate 向对象所有点的坐标添加一个平移。参数 deltaX 和 deltaY 分别指定水平和垂直平移的大小。正的 deltaX 值向右移动,deltaY 向上移动。Translate 不改变对象的形状或大小 — 仅改变其位置。
Scale 将点的坐标乘以给定的因子。在均匀缩放(单个参数)时,对象按比例放大或缩小。在非均匀缩放时,形状会变形。因子 1.0 保持尺寸不变,大于 1.0 放大,从 0.0 到 1.0 缩小。负值创建镜像。
Rotate 绕坐标原点将对象旋转一个给定角度。正旋转角度对应逆时针旋转。rotateByDegrees 接受度(0–360),rotateByRadians 接受弧度(0–2π)。旋转始终相对于点 (0, 0) 进行,因此要绕任意点旋转,需要将 rotate 与 translate 组合。
仿射变换 在 NSAffineTransform 中由一个 3×3 矩阵表示,其中最后一行始终为 [0, 0, 1]。这是一种标准的数学表示,可以将变换的组合表示为矩阵的乘积。矩阵的结构通过 NSAffineTransformStruct 定义,其包含六个重要分量:m11、m12、m21、m22、tX、tY。
m11、m12、m21、m22 分量负责缩放、旋转和剪切。tX 和 tY 分量负责平移。在标准配置中(单位矩阵),m11 = 1,m22 = 1,其余为 0。要获取矩阵的当前值,使用 transformStruct — NSAffineTransform 的属性,它返回 NSAffineTransformStruct 结构。
矩阵乘法是一种非交换运算。这意味着变换的应用顺序很重要:translate + rotate 的结果与 rotate + translate 不同。在组合时,NSAffineTransform 将新矩阵从左边乘以当前矩阵:newMatrix = operation × currentMatrix。因此,最后添加的变换第一个被应用。
NSAffineTransform 允许将多个变换组合在一个对象中。每次调用 translateXBy:yBy:、scaleBy: 或 rotateByDegrees: 都会修改内部矩阵 — 将当前矩阵乘以相应变换的矩阵。这可以从一系列简单操作中创建复杂的变换。
要将组合变换 取逆,使用 invert 方法。逆变换创建一个新的仿射变换,用于取消原始变换:如果将原始变换应用于一个点,然后再应用逆变换,点将回到初始位置。逆变换对于在绘制不应被变换的 UI 元素之前取消上下文变换很有用。
检查变换是否为 恒等变换(不改变坐标)可以通过 isIdentity 属性进行。恒等变换是单位矩阵,乘以它时点的坐标不会改变。新的 NSAffineTransform 对象创建时为恒等变换。这个属性对于优化很有用:如果变换是恒等的,可以跳过其应用。
NSAffineTransform 可以应用于四种类型的对象:NSPoint、NSSize、NSBezierPath 和 NSGraphicsContext 图形上下文。每个目标都有独立的方法:transformPoint:、transformSize:、transformBezierPath: 和 set。
方法 transformPoint: 将变换矩阵乘以点的坐标向量 (x, y, 1),返回点的新位置。transformSize: 类似工作,但不考虑 tX 和 tY 分量 — 平移不被应用,仅应用缩放、旋转和剪切。这对于不应移动的向量和尺寸的变换很有用。
方法 transformBezierPath: 创建一个新的 NSBezierPath 对象,其每个点都按照 NSAffineTransform 矩阵进行变换。原始路径不会改变 — 会创建一个带有变换坐标的拷贝。对于具有大量节点的复杂路径,这是一个成本高昂的操作。
方法 set 将变换应用于当前的图形上下文 (NSGraphicsContext),修改其 CTM(当前变换矩阵)。调用 set 后,在此上下文中的所有后续绘制都将被变换。对于临时变换,请在 set 之前和之后使用 saveGraphicsState / restoreGraphicsState。
让我们看看在 macOS 应用程序中使用 NSAffineTransform 进行图形对象变换的 实践示例。
示例创建了一个组合了平移、旋转和缩放的 NSAffineTransform。首先对象被移动 (100, 50),然后旋转 45 度,最后放大 1.5 倍。顺序很重要:在 translate 之后旋转会相对于新的坐标原点进行。
import AppKit
let transform = NSAffineTransform()
transform.translateXBy(100, yBy: 50)
transform.rotateByDegrees(45)
transform.scaleBy(1.5)
// 应用于点
let point = NSPoint(x: 10, y: 20)
let transformedPoint = transform.transform(point)
// 应用于尺寸
let size = NSSize(width: 100, height: 50)
let transformedSize = transform.transform(size)
示例展示了通过 set 和 concat 将 NSAffineTransform 应用于图形上下文。应用变换后,所有后续绘制都在变换后的坐标系中进行。通过 saveGraphicsState 保存图形状态可以返回到原始矩阵。
func drawWithTransform(in rect: NSRect) {
guard let ctx = NSGraphicsContext.current
else { return }
ctx.saveGraphicsState()
let xfm = NSAffineTransform()
xfm.translateXBy(rect.midX, yBy: rect.midY)
xfm.rotateByDegrees(30)
xfm.scaleBy(0.8)
xfm.concat()
// 在变换后的坐标系中绘制
let path = NSBezierPath(roundedRect: NSRect(
x: -50, y: -50,
width: 100, height: 100
), xRadius: 10, yRadius: 10)
NSColor.blue.setFill()
path.fill()
ctx.restoreGraphicsState()
}
示例展示了创建带有变换坐标的 NSBezierPath 拷贝。原始路径保持不变,变换后的拷贝可以单独绘制 — 这对于在不同位置创建重复元素很有用。
func createTransformedTriangle() -> NSBezierPath {
let triangle = NSBezierPath()
triangle.move(to: NSPoint(x: 0, y: 0))
triangle.line(to: NSPoint(x: 100, y: 0))
triangle.line(to: NSPoint(x: 50, y: 100))
triangle.close()
let xfm = NSAffineTransform()
xfm.translateXBy(200, yBy: 100)
xfm.scaleXBy(1.5, yBy: 1.5)
xfm.rotateByDegrees(45)
let transformed = xfm.transform(triangle)
return transformed
}
常见问题
NSAffineTransform — 是来自 AppKit 的类(仅 macOS),而 CGAffineTransform — 是来自 Core Graphics 的结构(iOS 和 macOS)。NSAffineTransform 是一个面向对象的封装,提供了处理 NSBezierPath 的方法。CGAffineTransform 是一个与 CGPath 一起使用的轻量级值类型结构。
要绕中心旋转,请执行 三个步骤:1)translate 到对象中心(将坐标原点移动到中心),2)rotate 旋转所需角度,3)translate 乘以中心的负向量回到原位。translate—rotate—translate 组合可以绕任意点旋转,而非仅仅绕坐标原点。
不可以,NSAffineTransform 仅在 macOS 的 AppKit 中可用。在 iOS 上,使用来自 Core Graphics 的 CGAffineTransform 结构,它提供了类似的功能:CGAffineTransformMakeTranslation、CGAffineTransformMakeScale、CGAffineTransformMakeRotation 和 CGAffineTransformConcat。
使用 invert 方法,它返回逆变换。如果将原始变换与逆变换相乘,会得到单位矩阵(恒等变换)。要临时应用,请在 set 之前调用 saveGraphicsState,之后调用 restoreGraphicsState。
使用 负缩放:transform.scaleXBy(-1, yBy: 1) 创建水平镜像,scaleXBy(1, yBy: -1) 创建垂直镜像。要相对于任意轴创建镜像,请组合 translate(移动到轴)、乘以 -1 的 scale 和逆 translate。
总结
invert 方法的 逆变换 取消任何仿射变换的效果我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。