Mysteries of Auto Layout, Part 2
1️⃣ View, Constraint, Property, IntrinsicContentSize 등을 구성한다.
2️⃣ Layout Engine이 계산을 한다.
3️⃣ 원하는 Layout 얻는다.
가운데 Layout engine에서 일어나는 Layout Cycle
레이아웃이 실제 바뀌어도 바로 UI에 적용되지 않는다.
(레이아웃 엔진의 변화와 뷰 계층에서의 변화에는 딜레이가 존재한다.)
먼저 constraint를 수정하면 레이아웃 엔진이 이를 알게 되고 이미 이곳에서 변한것을 알게되지만 UI에서 업데이트가 아직 안된다.
하지만 그 이후에 매치되어서 맞는 곳을 업데이트 시킨다.
constraints를 바꾸게하게 되면 무슨 일이 일어날까?
0️⃣ constraints 변경
1️⃣ 레이아웃 엔진이 다시 계산을 시작한다.(즉 특정 뷰의 origin, size 다시 확인하게된다.)
2️⃣ 그리고 다시 계산할 때 이 변수들이 다시 새로운 값이 나올 것이다.
3️⃣ 그래서 superview에게 layout 변화가 필요하다고 알려준다. 즉, View들이 superview.setNeedsLayout 를 호출
4️⃣ 이는 Deferred layout pass가 예약된다.
5️⃣ Layout enigne에서 frame이 실제로 변경 ➡️ View Hierarchy에서는 아직 변경되지 않음
💡 deferred layout pass가 오면
1 ) layout의 constraints를 업데이트 한 다음,
2 ) view 계층 구조의 모든 view에 대한 프레임을 계산한다.
라고 함.
Deferred Layout Pass는 실제로 view 계층 구조를 통과하는 2개의 Pass를 포함한다.
The update pass updates the constraints, as necessary == Update Constraints
The layout pass repositions the view’s frames, as necessary = Reassign view frames 이다.
⭐️ 즉, update pass + layout pass = Deferred Layout Pass
프레임이 레이아웃 엔진에서 변하면 구조에서는 안바뀌어서 아직 위치가 재조정되어 있지 않다.
이때 deferred layout pass를 통해 위치가 재조정되며 이것이 끝나게 되면 잘못 위치한 것을 올바른 곳으로 옮겨지게 된다.
pass1: 뷰는 메서드를(superview.setNeedsLayout()) 통해 요청을 한다.
pass2: 보통 next layout pass에 메서드가 호출되며 뷰의 레이아웃이 업데이트된다.
View는 updateConstratins() 호출을 명시적으로 요청하기 위하여 setNeedsUpdateConstaints() 를 호출
setNeedsUpdateConstraints() 를 호출하면 얼마 후 updateConstraints() 가 호출
이 모든 것은 View가 다음 Layout Pass를 위하여 제때에 Constraints를 변경하는 기회를 가질 수 있는 방법이지만, 실제로는 필요하지 않은 경우가 많다.
updateConstraints() 의 사용은 성능으로 귀결된다.
Update Constraints Pass가 완료되면 Constraints가 모두 최신 상태임을 알 수 있으며 View의 위치를 변경할 준비가 된 것이다.
여기에서 Top-Down으로 View Hierarchy 를 탐색하고 Layout이 필요한 것으로 표시된 모든 뷰에서 layoutSubviews() 를 호출
Constraints를 이용하여 표현할 수 없는 layout을 필요한 경우에만 layoutSubviews() 를 override
Custom Layout을 위하여 layoutSubViews() 을 override할 경우, 몇 가지 규칙이 존재한다.
🌟 Do
super.layoutSubviews() 호출
하위 트리 내의 layout을 무효화
❌ Don't
setNeedsUpdateconstraints() 를 호출하지 말아라.
하위 트리 외부의 layout을 무효화하지 말아라.
Layout feedback loop가 발생하며 무한 루프에 빠질 수 있다.t
무차별적으로 constraints를 수정하지 말아라.
결론적으로 Layout Cycle에서 기억해야할 부분이 있다.
1️⃣ 즉시 모든 프레임이 변할 것이라고 예측하면 안된다. constraints가 변하게 되면 과정을 걸쳐서 레이아웃이 변하게 되는 것이다. 그래서 즉시 변하지 않는 다는 것을 알아야한다!
2️⃣ layoutSubviews()를 override해서 사용하면 layout feedback loop에 빠질 수 있으니 조심해야한다.
원래는 autoresizing mask를 사용해서 레이아웃을 설정했다.
Autolayout를 활용해서 SuperView와의 레이아웃 설정을 해준다.
하지만 가끔 Views의 frame를 설정해주어야 할 때가 있다.
예를 들어 layoutSubviews를 overrride하게 되면 view의 frame를 설정이 필요할 때가 있다.
이거에 대한 flag가 있다. 이는 translateAutoresizingMaskIntoConstraints라는 프로퍼티다.
이것은 View에게 어떻게 행동할지를 정할 수 있는데 오토레이아웃에서 legacy Layout System에서 설정할 수 있다.
View의 프레임을 이 프로퍼티와 같이 설정하게 되면 프레임워크는 constraints를 만들고 layout engine에서 frame를 enforce한다.
이게 의미하는 바는 frame를 설정할 때 Auto layout를 count하고 뷰를 내가 넣을 곳에 유지한다.
게다가 이 constraints는 autoresizingmask과 유사하게 동작한다.
그리고 Auto layout engine 이용해서 뷰와 상대적인 위치를 파악해서 뷰의 frame를 설정해준다.
만약에 engine에게 어디에서 이 레이아웃이 필요하고 위치가 어딘지 말해주지 않는다면 시스템은 정확하게 파악할 수 없다.
코드로 작성할 때 frame을 직접 설정하는 경우, translateAutoresizingMaskIntoContraints를 false로 주지 않으면 resizingMask와 contraints 세팅한 것이 충돌나서 제대로 레이아웃이 세팅되지 않는다!
anchor는 factory method로 이전보다 읽기 편해짐
컴파일 에러로 알려줌(ex. location과 size끼리 제약사항 설정하려고 할 때)
이전의 비효율적인 레이아웃 구성: dummy view 사용
‼️ 해결: layoutGuide / layoutMarginGuide
새로운 방식의 layout guide class 제공한다.
예시 1:layout guide가 있다.
예시 2: layoutMarginGuide라는 것이 생기게 되었다.
이것들을 사용해서 레이아웃 에러를 해결할 수 있다.
이전의 방식보다 더 가볍고 뷰들의 뷰 구조를 망가트리지 않고도 레이아웃을 해줄 수 있다.
dummy View의 문제점은 뷰의 구조에 포함된다. 왜냐하면 empty View이기 떄문에 결국 다른 뷰와 동일하게 추가해야하기 떄문이다.
이는 결국 다른 뷰처럼 overhead에 추가되는 단점이 존재한다.
또한 다른 뷰들과 같이 있기 떄문에 가끔 message를 방해해서 문제가 생기면 어느 부분에서 생긴 것인지 파악이 어렵다.
그래서 나온 것이 LayoutGuide, layoutMarginGuide이다.
이런 경험 다들 해본적 있을 것이다.
왼쪽처럼 디자인했는데 실제로 실행해보면 오른쪽 처럼 나오는 것을 볼 수 있었을 것이다.
코드로 설정하는 법
명시적으로 identifier를 작성하면 레이아웃 모호하다고 에러 생겼을 때 확인 용이
IB에서 설정하는 법
contraintsAffectingLayoutForAxis 로 디버깅하기
➡️ 한 축 또는 다른 축에서 해당 view에 영향을 미치는 constraints만 알려준다.
언제 모호한 상황이 생길까?
-> 레이아웃 부족 or 너무 많을 때(conflict)
=> size 문제인지 location 문제인지 확인 가능
=> lldb 로 solution에 대한 힌트를 제안받을 수 있음
디버그 관련 Summary