NSAffineTransform — це клас із AppKit (macOS), що представляє афінне перетворення у двовимірній графіці. Він інкапсулює матрицю 3×3, яка описує комбінацію переміщення, масштабування, повороту та зсуву координат. За даними Apple Documentation (2026), NSAffineTransform широко використовується в Core Graphics для трансформації шляхів, точок і графічних контекстів на macOS.
Головне
NSAffineTransform — це клас із фреймворку AppKit, доступний лише на macOS. Він представляє афінне перетворення у двовимірному просторі через матрицю 3×3. На відміну від CGAffineTransform із Core Graphics (який доступний на iOS), NSAffineTransform є об'єктно-орієнтованою обгорткою над структурою CGAffineTransform, додаючи зручні методи для роботи з NSBezierPath, NSPoint та NSSize.
Афінне перетворення — це таке перетворення площини, яке зберігає відношення «точка лежить на прямій» і паралельність прямих. Іншими словами, після афінного перетворення паралельні лінії залишаються паралельними, а прямі — прямими. Однак відстані, кути та площі можуть змінюватися: масштабування, поворот і зсув є окремими випадками афінних перетворень.
На iOS аналогом NSAffineTransform є структура CGAffineTransform із Core Graphics. Обидва API засновані на одній математичній моделі — матриці афінного перетворення 3×3, де останній рядок завжди [0, 0, 1]. Відмінність у тому, що NSAffineTransform — це клас (із retain/release в Objective-C), а 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 (Current Transformation Matrix). Після виклику set вся подальша відмальовка в цьому контексті буде трансформуватися. Для тимчасової трансформації використовуйте saveGraphicsState / restoreGraphicsState до та після set.
Розглянемо практичні приклади використання NSAffineTransform у macOS-додатку для трансформації графічних об'єктів.
Приклад створює 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)
Приклад показує застосування 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()
// Малювання в трансформованій системі координат
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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також