두 번째 주제는 아핀 변환입니다. 개인적으로 생소한 용어였고 정의 자체를 이해하는데 상당한 시간이 걸렸는데요. 수학적인 내용은 잘 설명해드릴 수는 없지만 코드로 어떻게 사용하는지 결과가 어떻게 나타나는지 간략하게 정리해봤습니다.
= translation (평행 이동) + scaling (크기 변환) + skew (기울이기) + rotating (회전)
아핀 변환은 점, 직선, 평면을 보존하는 선형 매핑 방법입니다. 아핀 변환 후에도 평행한 두 선은 그대로 평행 상태를 유지한다는 특징이 있습니다. (더 이상은 묻지 마세요😅 )
⭐️ View 자체의 속성이기 때문에 layout constraints와는 관련이 없습니다.
아핀 변환을 적용할 2개의 사각형을 준비하겠습니다.
그리고 아핀 변환의 적용하는 버튼과 해제하는 애니메이션을 실행할 버튼도 하나 준비합니다.
import UIKit
class VC2: UIViewController {
// MARK: Properties
let redSquare: UIView = {
let view = UIView()
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
view.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.backgroundColor = .red
return view
}()
let blueSquare: UIView = {
let view = UIView()
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
view.heightAnchor.constraint(equalToConstant: 100).isActive = true
view.backgroundColor = .blue
return view
}()
let activatingButton: UIButton = {
let button = UIButton()
button.setTitle("Apply AffineTransform", for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(activateAffineTransform), for: .touchUpInside)
return button
}()
let deactivatingButton: UIButton = {
let button = UIButton()
button.setTitle("Remove AffineTransform", for: .normal)
button.setTitleColor(.black, for: .normal)
button.addTarget(self, action: #selector(deactivateAffineTransform), for: .touchUpInside)
return button
}()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
configureUI()
}
// MARK: Selector
@objc func activateAffineTransform() {
//✅ 아핀 변환 적용 애니메이션
}
@objc func deactivateAffineTransform() {
//✅ 아핀 변환 적용 애니메이션
}
// MARK: Helpers
func configureUI() {
view.addSubview(redSquare)
redSquare.translatesAutoresizingMaskIntoConstraints = false
redSquare.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
redSquare.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
view.addSubview(blueSquare)
blueSquare.translatesAutoresizingMaskIntoConstraints = false
blueSquare.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
blueSquare.topAnchor.constraint(equalTo: redSquare.bottomAnchor, constant: 100).isActive = true
view.addSubview(activatingButton)
activatingButton.translatesAutoresizingMaskIntoConstraints = false
activatingButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
activatingButton.topAnchor.constraint(equalTo: blueSquare.bottomAnchor, constant: 100).isActive = true
view.addSubview(deactivatingButton)
deactivatingButton.translatesAutoresizingMaskIntoConstraints = false
deactivatingButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
deactivatingButton.topAnchor.constraint(equalTo: activatingButton.bottomAnchor, constant: 20).isActive = true
}
}
아핀 변환은 UIView 객체의 transform 속성에 적용하면 됩니다. 할당하는 동시에 바로 해당 아핀 변환이 적용이 됩니다.
아래 아핀 변환은 평행 이동, 확대, 회전 3가지를 적용해보았습니다. 추가적인 API는 공식 문서를 참고해주세요.
@objc func activateAffineTransform() {
UIView.animate(withDuration: 2) {
self.redSquare.transform = CGAffineTransform(translationX: 0, y: 50)
.scaledBy(x: 2, y: 2)
.rotated(by: 90)
//👉 y축 방향으로 50만큼 평행이동, 가로세로 2배 확대, 90도 회전
self.blueSquare.transform = CGAffineTransform(translationX: 0, y: -50)
.scaledBy(x: 0.5, y: 0.5)
.rotated(by: 180)
//👉 y축 방향으로 -50만큼 평행이동, 가로세로 0.5배 확대, 180도 회전
}
}
UIView 객체의 transform 속성에 CGAffineTransform.identity를 적용하면 됩니다. CGAffineTransform.identity은 항등변환을 의미합니다.
@objc func deactivateAffineTransform() {
UIView.animate(withDuration: 2) {
self.redSquare.transform = CGAffineTransform.identity
self.blueSquare.transform = CGAffineTransform.identity
}
}
평행이동, 확대(축소), 회전이 동시에 일어나는 것을 볼 수 있습니다.
3차원 아핀 변환도 있습니다. 나중에 공부 해보고 포스팅에서 다뤄보도록 하겠습니다!