NSAffineTransform — to klasa z AppKit (macOS), reprezentująca przekształcenie afiniczne w grafice dwuwymiarowej. Hermetyzuje macierz 3×3, która opisuje kombinację przesunięcia, skalowania, obrotu i pochylenia współrzędnych. Według Apple Documentation (2026), NSAffineTransform jest szeroko używany w Core Graphics do transformacji ścieżek, punktów i kontekstów graficznych na macOS.
Główne
NSAffineTransform — to klasa z frameworku AppKit, dostępna tylko na macOS. Reprezentuje przekształcenie afiniczne w przestrzeni dwuwymiarowej poprzez macierz 3×3. W przeciwieństwie do CGAffineTransform z Core Graphics (który jest dostępny na iOS), NSAffineTransform jest obiektowo zorientowanym opakowaniem struktury CGAffineTransform, dodającym wygodne metody do pracy z NSBezierPath, NSPoint i NSSize.
Przekształcenie afiniczne — to takie przekształcenie płaszczyzny, które zachowuje relację „punkt leży na prostej” i równoległość prostych. Innymi słowy, po przekształceniu afinicznym linie równoległe pozostają równoległe, a proste — prostymi. Jednak odległości, kąty i pola mogą się zmieniać: skalowanie, obrót i pochylenie są szczególnymi przypadkami przekształceń afinicznych.
Na iOS odpowiednikiem NSAffineTransform jest struktura CGAffineTransform z Core Graphics. Oba API opierają się na tym samym modelu matematycznym — macierzy przekształcenia afinicznego 3×3, gdzie ostatni wiersz to zawsze [0, 0, 1]. Różnica polega na tym, że NSAffineTransform to klasa (z retain/release w Objective-C), a CGAffineTransform to struktura (typ wartościowy) w Swift i C.
NSAffineTransform obsługuje cztery podstawowe typy przekształceń: translate (przesunięcie), scale (skalowanie), rotate (obrót) i shear (pochylenie). Każdy typ jest reprezentowany przez osobną metodę i może być zastosowany zarówno do istniejącego obiektu transformacji, jak i użyty do utworzenia nowego.
| Operacja | Metoda NSAffineTransform | Parametry | Efekt wizualny |
|---|---|---|---|
| Translate | translateXBy:yBy: | deltaX, deltaY | Przesunięcie obiektu wzdłuż osi X i Y |
| Scale | scaleBy: / scaleXBy:yBy: | factor / scaleX, scaleY | Zwiększenie lub zmniejszenie rozmiarów |
| Rotate | rotateByDegrees: / rotateByRadians: | kąt w stopniach lub radianach | Obrót wokół początku układu współrzędnych |
| Shear | Przez setTransformStruct | składowe m12, m21 macierzy | Pochylenie obiektu wzdłuż osi |
Translate dodaje przesunięcie do współrzędnych wszystkich punktów obiektu. Parametry deltaX i deltaY określają wielkość przesunięcia odpowiednio w poziomie i pionie. Dodatnia wartość deltaX przesuwa w prawo, deltaY — w górę. Translate nie zmienia kształtu ani rozmiaru obiektu — tylko jego położenie.
Scale mnoży współrzędne punktów przez zadany współczynnik. Przy równomiernym skalowaniu (jeden parametr) obiekt zwiększa się lub zmniejsza proporcjonalnie. Przy nierównomiernym — kształt ulega zniekształceniu. Współczynnik 1.0 pozostawia rozmiar bez zmian, większy niż 1.0 — zwiększa, od 0.0 do 1.0 — zmniejsza. Wartości ujemne tworzą odbicie lustrzane.
Rotate obraca obiekt wokół początku układu współrzędnych o zadany kąt. Dodatni kąt obrotu odpowiada obrotowi przeciwnie do ruchu wskazówek zegara. rotateByDegrees przyjmuje stopnie (0–360), rotateByRadians — radiany (0–2π). Obrót jest zawsze wykonywany względem punktu (0, 0), dlatego do obrotu wokół dowolnego punktu należy połączyć rotate z translate.
Przekształcenie afiniczne w NSAffineTransform jest reprezentowane przez macierz 3×3, gdzie ostatni wiersz to zawsze [0, 0, 1]. Jest to standardowa reprezentacja matematyczna, umożliwiająca przedstawienie kombinacji przekształceń jako iloczynu macierzy. Struktura macierzy jest określana przez NSAffineTransformStruct, która zawiera sześć znaczących składników: m11, m12, m21, m22, tX, tY.
Składniki m11, m12, m21, m22 odpowiadają za skalowanie, obrót i pochylenie. Składniki tX i tY — za przesunięcie. W standardowej konfiguracji (macierz jednostkowa) m11 = 1, m22 = 1, pozostałe są równe 0. Do pobrania bieżących wartości macierzy używa się transformStruct — właściwości NSAffineTransform zwracającej strukturę NSAffineTransformStruct.
Mnożenie macierzy jest operacją nieprzemienną. Oznacza to, że kolejność stosowania przekształceń ma znaczenie: translate + rotate daje inny wynik niż rotate + translate. Podczas łączenia NSAffineTransform mnoży nową macierz z lewej strony przez bieżącą: newMatrix = operation × currentMatrix. Dlatego ostatnie dodane przekształcenie jest stosowane jako pierwsze.
NSAffineTransform pozwala łączyć wiele przekształceń w jednym obiekcie. Każde wywołanie translateXBy:yBy:, scaleBy: lub rotateByDegrees: modyfikuje wewnętrzną macierz — mnoży bieżącą macierz przez macierz odpowiedniego przekształcenia. Umożliwia to utworzenie złożonego przekształcenia z sekwencji prostych operacji.
Do odwracania złożonego przekształcenia używa się metody invert. Inwersja tworzy nowe przekształcenie afiniczne, które anuluje oryginalne: jeśli zastosujemy do punktu oryginalne przekształcenie, a następnie odwrócone, punkt powróci do początkowego położenia. Inwersja jest przydatna do cofnięcia transformacji kontekstu przed rysowaniem elementów UI, które nie powinny być przekształcone.
Sprawdzenie, czy przekształcenie jest tożsamościowe (nie zmienia współrzędnych), można wykonać przez właściwość isIdentity. Przekształcenie tożsamościowe to macierz jednostkowa, przy mnożeniu przez którą współrzędne punktu nie ulegają zmianie. Nowy obiekt NSAffineTransform jest tworzony jako przekształcenie tożsamościowe. Ta właściwość jest przydatna do optymalizacji: jeśli przekształcenie jest tożsamościowe, można pominąć jego zastosowanie.
NSAffineTransform może być zastosowany do czterech typów obiektów: NSPoint, NSSize, NSBezierPath i kontekstu graficznego NSGraphicsContext. Dla każdego celu istnieje osobna metoda: transformPoint:, transformSize:, transformBezierPath: i set.
Metoda transformPoint: mnoży macierz przekształcenia przez wektor współrzędnych punktu (x, y, 1), zwracając nowe położenie punktu. transformSize: działa analogicznie, ale nie uwzględnia składników tX i tY — przesunięcie nie jest stosowane, tylko skalowanie, obrót i pochylenie. Jest to przydatne do transformacji wektorów i rozmiarów, które nie powinny być przesuwane.
Metoda transformBezierPath: tworzy nowy obiekt NSBezierPath, którego każdy punkt jest przekształcony zgodnie z macierzą NSAffineTransform. Oryginalna ścieżka nie ulega zmianie — tworzona jest kopia z przekształconymi współrzędnymi. Jest to kosztowna operacja dla złożonych ścieżek z dużą liczbą węzłów.
Metoda set stosuje przekształcenie do bieżącego kontekstu graficznego (NSGraphicsContext), modyfikując jego CTM (Current Transformation Matrix). Po wywołaniu set całe późniejsze rysowanie w tym kontekście będzie przekształcane. Do tymczasowego przekształcenia użyj saveGraphicsState / restoreGraphicsState przed i po set.
Rozważmy praktyczne przykłady użycia NSAffineTransform w aplikacji macOS do transformacji obiektów graficznych.
Przykład tworzy NSAffineTransform łączący przesunięcie, obrót i skalowanie. Najpierw obiekt jest przesuwany o (100, 50), następnie obracany o 45 stopni, a na końcu zwiększany 1.5 raza. Kolejność ma znaczenie: rotate po translate powoduje obrót względem nowego początku układu współrzędnych.
import AppKit
let transform = NSAffineTransform()
transform.translateXBy(100, yBy: 50)
transform.rotateByDegrees(45)
transform.scaleBy(1.5)
// Zastosowanie do punktu
let point = NSPoint(x: 10, y: 20)
let transformedPoint = transform.transform(point)
// Zastosowanie do rozmiaru
let size = NSSize(width: 100, height: 50)
let transformedSize = transform.transform(size)
Przykład pokazuje zastosowanie NSAffineTransform do kontekstu graficznego przez set i concat. Po zastosowaniu transformacji całe późniejsze rysowanie odbywa się w przekształconym układzie współrzędnych. Zapisanie stanu grafiki przez saveGraphicsState umożliwia powrót do oryginalnej macierzy.
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()
// Rysowanie w przekształconym układzie współrzędnych
let path = NSBezierPath(roundedRect: NSRect(
x: -50, y: -50,
width: 100, height: 100
), xRadius: 10, yRadius: 10)
NSColor.blue.setFill()
path.fill()
ctx.restoreGraphicsState()
}
Przykład demonstruje tworzenie kopii NSBezierPath z przekształconymi współrzędnymi. Oryginalna ścieżka pozostaje bez zmian, a przekształcona kopia może być rysowana osobno — jest to przydatne do tworzenia powtarzających się elementów w różnych położeniach.
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
}
Często zadawane pytania
NSAffineTransform — to klasa z AppKit (tylko macOS), a CGAffineTransform — to struktura z Core Graphics (iOS i macOS). NSAffineTransform jest obiektowo zorientowanym opakowaniem i udostępnia metody do pracy z NSBezierPath. CGAffineTransform to lekkostruktura typu wartościowego używana z CGPath.
Aby obrócić wokół środka, wykonaj trzy kroki: 1) translate do środka obiektu (przesunięcie początku układu do środka), 2) rotate o żądany kąt, 3) translate z powrotem o ujemny wektor środka. Kombinacja translate—rotate—translate daje obrót wokół dowolnego punktu, a nie wokół początku układu współrzędnych.
Nie, NSAffineTransform jest dostępny tylko na macOS w ramach AppKit. Na iOS używa się struktury CGAffineTransform z Core Graphics, która zapewnia analogiczne możliwości: CGAffineTransformMakeTranslation, CGAffineTransformMakeScale, CGAffineTransformMakeRotation i CGAffineTransformConcat.
Użyj metody invert, która zwraca odwrotne przekształcenie. Jeśli pomnożymy oryginalne przekształcenie przez odwrócone, otrzymamy macierz jednostkową (przekształcenie tożsamościowe). Do tymczasowego zastosowania wywołaj saveGraphicsState przed set i restoreGraphicsState po.
Użyj ujemnej skali: transform.scaleXBy(-1, yBy: 1) tworzy odbicie w poziomie, a scaleXBy(1, yBy: -1) — w pionie. Do odbicia względem dowolnej osi połącz translate (przesunięcie na oś), scale z -1 i odwrotny translate.
Podsumowanie
invert anuluje działanie dowolnego przekształcenia afinicznegoOpracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również