디바이스 회전 대응

👋 들어가기 전
최근 들어 iPad 앱을 도전해보고 싶은 마음이 점점 커지고 있다.
그래서 앱에서 디바이스를 회전했을 때 대응하는 과정을 한번 학습해보고 싶었다.
🏁 학습할 내용
먼저 가장 필요한 내용이 화면 회전 시 호출될 생명주기와
대응코드가 들어갈 위치를 알아보자.
이후 엣지 케이스 대응을 위한 추가학습 미리보기까지 정리해보자.
- viewWillTransition
- layoutSubview
🔄 viewWillTransition
⭐️ 정의
공식문서를 살펴보면 viewWillTransition은 뷰들을 포함하는 컨테이너의 사이즈가
변경되면 호출된다고 써 있다.
가장 대표적인 컨테이너 사이즈 변화는 디바이스 회전이 있다.
아이패드 일 때는 스플릿 뷰도 포함된다.
🔥 구성요소
override func viewWillTransition(
to size: CGSize,
with coordinator: any UIViewControllerTransitionCoordinator
) {
}
1. size
- 컨테이너의 변화된 새로운 사이즈
2. coordinator
- 전환에 대한 진행 정보를 제공하고, 변화를 관리하는 객체
⌨️ 실습
✅ 증명
먼저 언제 호출되는 지 살펴보자.
위에서 설명한대로 회전과 스플릿을 진행

예상 했던대로 회전과 스플릿 뷰에서 모두 호출된는 것을 검증했다.
🍎 animate vs animateAlongsideTransition
coordinator에서 제공하는 api를 보면 animation과 관련된 2개의 메서드가 있다.
역활과 차이점을 살펴보자.

| 구분 | animate | animateAlongsideTransition |
| 스타일 | 최신 | 구버전 |
| 여갛ㄹ | 전환 애니메이션에서 추가로 실행될 애니메이션 적용 | |
🖼️ layoutSubviews()
⭐️ 정의
자신의 자식 뷰들의 배치를 잡을 때 호출된다.
여기서 배치를 잡을 때는 재배치도 포함된다.
재배치는 언제 이뤄질까?
프레임 크기가 변경되거나 제약조건이 변경되거나 다양한 경우가 존재하지만
결국에는 시스템이 updateCycle 시 갱신이 필요하다는 변화가 느껴져야 호출된다.
⌨️ 실습
한번 지금까지 배운 개념을 잠깐 적용해보자.
가로모드와 세로모드일 때 AView의 높이를 변경해보고
layoutSubviews가 호출되는 지 살펴보자.
AView.swift
class AView: UIView {
private let label: UILabel = {
let label = UILabel()
label.text = "123123124141"
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
print(#function)
}
init() {
super.init(frame: .zero)
self.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
label.textColor = .blue
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController.swift
final class ViewController: UIViewController {
private let aView = AView()
private var portraitConstraints: NSLayoutConstraint?
private var landscapeConstraints: NSLayoutConstraint?
override func viewWillTransition(
to size: CGSize,
with coordinator: any UIViewControllerTransitionCoordinator
) {
let isPortrait = size.width < size.height
coordinator.animate(alongsideTransition: nil) { [weak self] _ in
guard let self else { return }
if isPortrait {
landscapeConstraints?.isActive = false
portraitConstraints?.isActive = true
} else {
portraitConstraints?.isActive = false
landscapeConstraints?.isActive = true
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(aView)
aView.translatesAutoresizingMaskIntoConstraints = false
aView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
aView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
aView.backgroundColor = .black
portraitConstraints = aView.heightAnchor.constraint(equalToConstant: 100)
landscapeConstraints = aView.heightAnchor.constraint(equalToConstant: 200)
portraitConstraints?.isActive = true
}
}
결과
예측한 동작이 작동해서 너무 좋다...

❓그러면 여기서 질문
UICollectionView와 UITableView와 같은 cell을 통한 데이터를 표현할 때
cell들의 layoutSubview()를 어떻게 호출하고
데이터는 그대로이고 레이아웃의 변화가 일어날 때 어떤 일들이 일어날까??
그 내용은 UICollectionView와 UITableView 파트로 나눠서 다음 포스팅에서 진행하겠다..
😀 소감 및 마무리
언젠간 꼭 이 지식들을 이용해 가로모드도 자연스럽게 지원되는 앱을 만들고 싶다.
출처
https://developer.apple.com/documentation/uikit/uicontentcontainer/viewwilltransition(to:with:)
viewWillTransition(to:with:) | Apple Developer Documentation
Notifies the container that the size of its view is about to change.
developer.apple.com
https://developer.apple.com/documentation/uikit/uiview/layoutsubviews()
layoutSubviews() | Apple Developer Documentation
Lays out subviews.
developer.apple.com
생명주기 (3) [ View 생명주기 ]
오늘은 iOS 생명주기의 마지막 단계인 View의 생명주기에 대해 알아보자. 전체적인 생명주기는 다음 그림과 같다. init1) init((coder:))스토리보드나 xib파일은 활용하여 화면을 만들 때 컴파일러가
hamp.tistory.com
생명주기 (4) [ 업데이트 Cycle ]
애니메이션과 트랜지션과 같은 조금 더 개선된 UX를 주기 위해 다양한 코드를 적었는데 원하는 대로 동작하지 않는 경우가 자주 발생하고 나는 그게 View Rendenring을 재대로 이해하지 못하고 있는
hamp.tistory.com