NSAffineTransform — это класс из AppKit (macOS), представляющий аффинное преобразование в двумерной графике. Он инкапсулирует матрицу 3×3, которая описывает комбинацию перемещения, масштабирования, поворота и сдвига координат. По данным Apple Documentation (2026), NSAffineTransform широко используется в Core Graphics для трансформации путей, точек и графических контекстов на macOS.
Главное
NSAffineTransform — это класс из фреймворка AppKit, доступный только на macOS. Он представляет аффинное преобразование в двумерном пространстве через матрицу 3×3. В отличие от CGAffineTransform из Core Graphics (который доступен на iOS), NSAffineTransform является объектно-ориентированной обёрткой над struct CGAffineTransform, добавляя удобные методы для работы с NSBezierPath, NSPoint и NSSize.
Аффинное преобразование — это такое преобразование плоскости, которое сохраняет отношение "точка лежит на прямой" и параллельность прямых. Другими словами, после аффинного преобразования параллельные линии остаются параллельными, а прямые — прямыми. Однако расстояния, углы и площади могут меняться: масштабирование, поворот и сдвиг являются частными случаями аффинных преобразований.
На iOS аналогом NSAffineTransform является структура CGAffineTransform из Core Graphics. Оба API основаны на одной математической модели — матрице аффинного преобразования 3×3, где последняя строка всегда [0, 0, 1]. Различие в том, что NSAffineTransform — это класс (с retain/release в Objective-C), а CGAffineTransform — это struct (значимый тип) в 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: | angle в градусах или радианах | Поворот вокруг начала координат |
| 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 (Current Transformation Matrix). После вызова set вся последующая отрисовка в этом контексте будет трансформироваться. Для временной трансформации используйте saveGraphicsState / restoreGraphicsState до и после set.
Рассмотрим практические примеры использования NSAffineTransform в macOS-приложении для трансформации графических объектов.
Пример создаёт NSAffineTransform, комбинирующий сдвиг, поворот и масштабирование. Сначала объект смещается на (100, 50), затем поворачивается на 45 градусов и, наконец, увеличивается в 1.5 раза. Важен порядок: rotate после translate — поворот будет относительно нового начала координат.
import AppKit
let transform = NSAffineTransform()
transform.translateXBy(100, yBy: 50)
transform.rotateByDegrees(45)
transform.scaleBy(1.5)
// Applying to point
let point = NSPoint(x: 10, y: 20)
let transformedPoint = transform.transform(point)
// Applying to size
let size = NSSize(width: 100, height: 50)
let transformedSize = transform.transform(size)
Пример показывает применение NSAffineTransform к графическому контексту через set и concat. После применения трансформации вся последующая отрисовка выполняется в трансформированной системе координат. Сохранение состояния графики через 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()
// Drawing in transformed coordinate system
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 — легковесная value-тип структура, используемая с CGPath.
Для поворота вокруг центра выполните три шага: 1) translate на центр объекта (смещение начала координат в центр), 2) rotate на нужный угол, 3) translate обратно на отрицательный вектор центра. Комбинация translate—rotate—translate даёт вращение вокруг произвольной точки, а не вокруг начала координат.
Нет, NSAffineTransform доступен только на macOS в составе AppKit. На iOS используется структура CGAffineTransform из Core Graphics, которая предоставляет аналогичные возможности: CGAffineTransformMakeTranslation, CGAffineTransformMakeScale, CGAffineTransformMakeRotation и CGAffineTransformConcat.
Используйте метод invert, который возвращает обратное преобразование. Если умножить исходное преобразование на инвертированное, получится единичная матрица (тождественное преобразование). Для временного применения вызовите saveGraphicsState до set и restoreGraphicsState после.
Используйте отрицательный масштаб: transform.scaleXBy(-1, yBy: 1) создаёт отражение по горизонтали, а scaleXBy(1, yBy: -1) — по вертикали. Для отражения относительно произвольной оси скомбинируйте translate (смещение на ось), scale с -1 и обратный translate.
Итоги
invert отменяет действие любого аффинного преобразованияМы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также