CGAffineTransform 분석하기 – 2D 변환의 모든 것

안녕하세요 🙂
오늘은 iOS 개발할 때 정말 자주 마주치는 CGAffineTransform에 대해 이야기해볼게요
처음 보면 "이게 뭐야??" 싶지만, 알고 나면 진짜 유용한 녀석이에요
CGAffineTransform은 한마디로 2D 그래픽 변환을 담당하는 구조체예요
뷰를 이동시키거나, 회전시키거나, 크기를 바꾸거나 할 때 사용하는데요
이름만 봐도 알 수 있듯이 "아핀 변환(Affine Transform)"을 다루는 녀석이에요
아핀 변환이 뭐냐구요??
간단하게 말하면 "직선은 직선으로, 평행선은 평행선으로" 유지되는 변환이에요
이미지 출처: lidium - 티스토리
CGAffineTransform의 수학적 구조
내부적으로는 3x3 행렬로 표현돼요
// 행렬 구조
[a b 0]
[c d 0]
[tx ty 1]
여기서 a, b, c, d는 회전/스케일/기울이기를 담당하고
tx, ty는 이동을 담당해요
이 행렬이 점 (x, y)에 적용되면 이렇게 계산돼요
x' = a * x + c * y + tx
y' = b * x + d * y + ty
수학적인 얘기는 여기까지 하고~ 실제로 어떻게 쓰는지 볼게요!
기본 변환 타입들
CGAffineTransform은 주로 네 가지 방식으로 만들어요
1. 이동 (Translation)
let moveTransform = CGAffineTransform(translationX: 100, y: 50)
myView.transform = moveTransform
// 뷰가 x축으로 100, y축으로 50만큼 이동해요
2. 확대/축소 (Scale)
let scaleTransform = CGAffineTransform(scaleX: 2.0, y: 2.0)
myView.transform = scaleTransform
// 뷰가 가로세로 2배로 커져요
3. 회전 (Rotation)
let rotateTransform = CGAffineTransform(rotationAngle: .pi / 4)
myView.transform = rotateTransform
// 뷰가 45도 회전해요 (라디안 단위!)
각도를 degree로 쓰고 싶다면 이렇게 변환해주면 돼요
let angle = 45 * CGFloat.pi / 180
let rotateTransform = CGAffineTransform(rotationAngle: angle)
4. 직접 행렬 지정
let customTransform = CGAffineTransform(a: 1, b: 0, c: 0.5, d: 1, tx: 0, ty: 0)
// 기울이기 같은 복잡한 변환을 직접 만들 때 써요

이미지 출처: velog
변환 조합하기 - 여기가 핵심이에요!
여러 변환을 동시에 적용하고 싶을 때가 있잖아요??
그럴 땐 concatenating을 사용해요
let scale = CGAffineTransform(scaleX: 2.0, y: 2.0)
let rotate = CGAffineTransform(rotationAngle: .pi)
let move = CGAffineTransform(translationX: 0, y: -200)
let combined = scale
.concatenating(rotate)
.concatenating(move)
myView.transform = combined
근데 여기서 진짜 중요한 포인트!!!
변환은 왼쪽에서 오른쪽 순서로 적용돼요
위 코드는 Scale → Rotate → Translate 순서로 적용된다는 거죠
순서를 바꾸면 결과가 완전 달라져요
행렬 곱셈이라서 교환법칙이 성립하지 않거든요
// 이렇게 하면 결과가 달라요
let combined2 = move
.concatenating(rotate)
.concatenating(scale)
// Translate → Rotate → Scale 순서
그래서 변환을 분석할 때는 이 순서를 잘 파악하는 게 정말 중요해요!
애니메이션과 함께 사용하기
CGAffineTransform은 애니메이션이랑 찰떡궁합이에요
UIView.animate(withDuration: 1.0) {
titleLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}
// 1초 동안 1.5배로 커지는 애니메이션
여러 변환을 동시에 애니메이션할 수도 있어요
UIView.animate(withDuration: 2.0) {
let scale = CGAffineTransform(scaleX: 2, y: 2)
let rotate = CGAffineTransform(rotationAngle: .pi)
let move = CGAffineTransform(translationX: 200, y: 200)
let combo = scale
.concatenating(rotate)
.concatenating(move)
myView.transform = combo
}
// 커지면서 회전하고 이동하는 애니메이션
이미지 출처: 김종권의 iOS 앱 개발 알아가기 - 티스토리
원래 상태로 돌리기 - identity
변환을 적용했다가 다시 원래대로 돌리고 싶을 때는 identity를 써요
UIView.animate(withDuration: 1.0, animations: {
titleLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}) { _ in
UIView.animate(withDuration: 1.0) {
titleLabel.transform = .identity
}
}
// 커졌다가 원래대로 돌아오는 애니메이션
identity는 단위 행렬이에요
모든 변환을 제거하고 원래 프레임 기준 상태로 돌려줘요
실무에서 주의할 점들
1. frame vs transform
frame을 직접 바꾸면 레이아웃 정보가 바뀌는데
transform은 렌더링 결과만 변형해요
오토레이아웃 쓸 때는 이 차이를 꼭 알고 있어야 해요!
transform은 constraint에 영향을 안 주거든요
// frame 변경 - 오토레이아웃이랑 충돌할 수 있어요
myView.frame.size = CGSize(width: 200, height: 200)
// transform 변경 - 오토레이아웃은 그대로, 보이는 것만 바뀌어요
myView.transform = CGAffineTransform(scaleX: 2, y: 2)
2. 좌표계 기준 이해하기
변환은 상위 뷰의 좌표계 기준으로 적용돼요
상위 뷰에 변환을 주면 자식 뷰들도 같이 변환돼요
containerView.transform = CGAffineTransform(rotationAngle: .pi / 4)
// containerView 안의 모든 자식 뷰가 같이 45도 회전해요
3. 이미지 처리할 때도 유용해요
// 이미지 상하 반전
let flipTransform = CGAffineTransform(scaleX: 1.0, y: -1.0)
context.concatenate(flipTransform)
이미지 출처: 김종권의 iOS 앱 개발 알아가기 - 티스토리
CGAffineTransform 분석 체크리스트
코드에서 복잡한 transform을 만났을 때 이렇게 분석해보세요
1. 최종 transform 표현식 찾기
- concatenating 체인이 어떻게 연결되어 있는지
2. 적용 순서 파악하기
- 왼쪽에서 오른쪽으로 차례대로 적용된다는 걸 기억!
3. 각 단계별 변화 추적하기
- Scale → Rotate → Translate 순서로 점이 어떻게 움직이는지
4. identity와 재사용 확인
- 애니메이션 후 identity로 되돌리는지
- 기존 transform에 추가로 적용하는지
마무리
CGAffineTransform은 처음엔 어려워 보이지만
원리만 이해하면 정말 강력한 도구예요
특히 애니메이션 만들 때 frame을 직접 건드리는 것보다
transform을 쓰는 게 훨씬 깔끔하고 자연스러워요
여러분도 다음 프로젝트에서 한번 써보세요!
뷰에 생동감을 불어넣는 재미를 느낄 수 있을 거예요
참고 문서:
https://developer.apple.com/documentation/coregraphics/cgaffinetransform
이전 글 보기