Written by
doremin
on
on
Transform은 순서가 중요하다.
Transform은 순서가 중요하다.
UIKit에서 CGAffineTransform
을 사용할 때는 transform의 적용 순서에 따라 결과가 완전히 달라진다.
아래 두 개의 코드 예시를 보자.
겉보기에는 결과가 같아 보이지만, 실제 동작은 전혀 다르다.
box1.transform = CGAffineTransform.identity
.scaledBy(x: 2, y: 2)
.rotated(by: .pi / 4)
.translatedBy(x: 100, y: 0)
box2.transform = CGAffineTransform.identity
.translatedBy(x: 100, y: 0)
.rotated(by: .pi / 4)
.scaledBy(x: 2, y: 2)
왜 순서에 따라 결과가 달라질까?
이 그림은 객체를 이동한 후 회전했을 때, 결과가 어떻게 달라지는지를 보여준다.
translate
, rotate
, scale
연산은 모두 원점을 기준으로 수행되기 때문에, 순서에 따라 시각적으로 완전히 다른 결과가 나타날 수 있다.
일반적으로는 다음 순서를 따르는 것이 안정적이다:
- Scale → Rotate → Translate
이 순서는 column-major 행렬 곱 표현으로 다음과 같이 나타낼 수 있다:
$$
x' = T \cdot R \cdot S \cdot x
$$
왜 오른쪽 아래로 이동했을까?
아래 코드를 살펴보자. translate
에는 y 값이 없지만, 결과적으로 y 방향으로도 이동한 것처럼 보인다.
let transform = CGAffineTransform.identity
.translatedBy(x: 100, y: 0)
.rotated(by: .pi / 4)
.scaledBy(x: 2, y: 2)
이 코드를 적용하면 다음과 같은 과정이 진행된다.
- 먼저
(0, 0)
→(100, 0)
으로 이동 (Translation) - 이후 원점 기준으로
(100, 0)
을 45도 회전
결과:x' = cos(π/4) * 100 = 70.7 y' = sin(π/4) * 100 = 70.7
- 마지막으로 Scale:
x' * 2 = 141.4 y' * 2 = 141.4
결과적으로 오른쪽 아래 대각선 방향으로 이동한 것처럼 보이게 된다.
올바른 순서로 적용하고 싶다면?
let transform = CGAffineTransform.identity
.scaledBy(x: 2, y: 2)
.rotated(by: .pi / 4)
.translatedBy(x: 100, y: 0)
이렇게 작성하면:
- 먼저 스케일을 적용하고
- 그다음 회전을 수행한 뒤
- 마지막으로 위치를 이동한다
즉, S → R → T 순서를 따르는 구조이다.
실전 팁
- transform 결과가 의도와 다르다면 회전과 스케일의 기준점(anchor) 을 먼저 확인하자.
anchorPoint
를 바꾸면 transform의 중심 기준을 변경할 수 있다.translate → rotate
순서로 연산하면, 원점을 기준으로 회전하는 효과를 줄 수 있다 ..!
마무리
UIKit의 transform 역시 2D 그래픽스의 행렬 연산이다.
행렬의 곱셈 순서가 바뀌면 결과도 완전히 달라지기 때문에,
코드상의 순서 하나하나가 실제 화면의 위치와 회전에 큰 영향을 미친다.