The Layout Cycle
Inside the Black Box
- Black box의 내부에서는 아래와 같이 진행된다.
- View, Constraint, Property, IntrinsicContentSize 등을 구성한다.
- Layout Engine이 계산을 한다.
- Layout 얻는다.
The Layout Cycle
TL;DR
frame
이 즉각적으로 변경될 것이라고 기대하지 말아라.
layoutSubViews()
를 override할 때 주의를 기울여라.
- 우리는 계산된 layout이 달라질 필요가 있는 Cosntraints가 바뀔 때까지 Application Run Loop를 반복하는 것으로 시작한다.
- 이로 인해 Deferred Layout Pass가 예약된다.
- Layout Pass가 나오면, Hierarchy를 살펴보고 View에 대한 모든 frame을 업데이트한다.
The Layout Cycle - Example
- Layout을 변경하면 layout engine의 모든 것은 이미 변경되었지만 UI는 아직 업데이트되지 않은 상태이다.
- 그런 다음 Layout Pass가 진행되면, UI가 실제로 변경된다.
- UI는 실제로 Layout Engine이 생각하는 것과 일치하도록 변경된다.
Constraint Changes
-
생성된 Constraints는 수학적 표현식으로 변환되고 Layout Engine 내에 유지된다.
-
표현식은 모든 constraints의 변경에 영향을 받는다.
- Constraints를 activate / deactivate하거나 constraints의 우선 순위 또는 상수를 변경하는 것과 같은 몇 가지 명백한 사항이 포함된다.
- 또한, View hierarchy를 조작하거나 재구성하는 것(view의 추가 및 삭제)과 같은 덜 분명한 것도 포함된다.
- 이는 간접적으로 cosntraints의 변경을 유발할 수 있다.
- 표현식은 특정 View의
origin
이나 size
와 같은 것을 나타내는 변수로 구성된다.
-
Constraint가 변경되면, Layout Engine이 layout을 재계산한다.
- Layout이 재계산될 때, 표현식의 변수들은 새로운 값을 받는다.
- 이런 일이 발생하면, 그들(
변수? 이것이 지칭하는 무엇일까)이 나타내는 view들에 알림이 전송되고, superview
가 layout이 필요한 것으로 표시된다.
- 즉, View들이
superview.setNeedsLayout
를 호출한다.
- 이것이 실제로 "Deferred Layout Pass"가 예약되는 원인이다.
- Layout enigne에서 frame이 실제로 변경되지만 View Hierarchy에서는 아직 변경되지 않는 곳이다.
Deferred Layout Pass
- 올바른 위치에 있지 않은 모든 View의 위치를 변경 하도록 한다.
- Deferred Layout Pass에는 View hierarchy를 통과하는 "Update Constraints", "Reassign view frames"가 있다.
-
Constraints에 대한 보류 중인 변경 사항이 있으면, View Hierarchy를 통과하여 모든 View의 위치를 변경하기 전에 해당 변경사항이 지금 발생하는지 확인한다.
-
View는 updateConstratins()
호출을 명시적으로 요청하기 위하여 setNeedsUpdateConstaints()
를 호출한다.
-
그리고 이것은 setNeedsDisplay()
와 거의 같은 방식으로 작동한다.
-
setNeedsUpdateConstraints()
를 호출하면 얼마 후 updateConstraints()
가 호출된다.
-
이 모든 것은 View가 다음 Layout Pass를 위하여 제때에 Constraints를 변경하는 기회를 가질 수 있는 방법이지만, 실제로는 필요하지 않은 경우가 많다.
- 이상적으로, 최초의 constriants 설정은 Interface Builder에서 발생하여야 한다.
- 만일 정말 constraints를 프로그래밍적으로 할당해야하는 경우에는
viewDidLoad
와 같은 위치가 훨씬 좋다.
- 반면에, 해당 로직을 연관된 다른 코드와 분리하여 나중에 실행되는 별도의 메소드로 옮긴다면, 코드를 따라가기 훨씬 어려워지므로 가독성과 유지 관리가 어려워진다.
-
updateConstraints()
의 사용은 성능으로 귀결된다.
- Constraints을 변경하는 것이 너무 느리다면
updateConstraints()
가 도움이 될 수 있다.
- Layout Engine이
updateCosntraint
에서 발생하는 모든 constraint change를 batch로 처리할 수 있기 때문에, updateConstraints()
내에서 constraints를 변경하는 것이 다른 시간에 contstraints를 변경하는 것보다 더 빠르다.
- 이는 Cosntratins 각각을 개별적으로 activate하는 것과는 대조적으로 Constraints Array에 대해
NSLayoutConstraint.activate()
를 호출하여 얻는 것과 동일한 종류의 성능 이점이다.
-
Update Constraints Pass가 완료되면 Constraints가 모두 최신 상태임을 알 수 있으며 View의 위치를 변경할 준비가 된 것이다.
Reassign view frames
- 여기에서 Top-Down으로 View Hierarchy 를 탐색하고 Layout이 필요한 것으로 표시된 모든 뷰에서
layoutSubviews()
를 호출한다.
- Reciever들의 위치를 재조정하기 위한 것이 아닌, subview의 위치를 재조정하기 위하여 호출하도록 한다.
- Layout Engine으로부터 subview들에 대한 frame을 읽은 다음 할당한다.
- Constraints를 이용하여 표현할 수 없는 layout을 필요한 경우에만
layoutSubviews()
를 override하여야 한다.
- override를 할 경우, 어떠한 view들은 이미 배치되어져 있지만, 또다른 view들은 그렇지 않을 수 있다.
- 배치되지 않은 view도 아마 곧 배치될 것이므로 약간 섬세한 순간이다.
- Custom Layout을 위하여
layoutSubViews()
을 override할 경우, 몇 가지 규칙이 존재한다.
- 🌟 Do
super.layoutSubviews()
호출하라.
- 하위 트리 내의 layout을 무효화하라.
- ❌ Don't
setNeedsUpdateconstraints()
를 호출하지 말아라.
- 하위 트리 외부의 layout을 무효화하지 말아라.
- Layout feedback loop가 발생하며 무한 루프에 빠질 수 있다.t
- 무차별적으로 constraints를 수정하지 말아라.
Interacting with Legacy Layout
- 과거부터 현재까지, Layout을 구성하는 방법은 변경되어져 왔다.
- 전통적으로 frame을 설정하는 것만으로 view를 배치하였다.
- 다음은, superview의 size가 변경될 때 view의 size를 조정하는 방법을 지정하는
autoresizingMask
가 있었다.
- 다음으로, AutoLayout에서 constraints으로 모든 작업을 수행하게 되었다.
- 여전히
view.frame
을 직접 set하면 위치로 이동을 하지만, Framework가 Layout Engine에서 frame을 복사하여 적용하는 경우, 언제든지 해당 frame을 덮어 쓸 수 있다.
- 문제는 가끔 frame을 설정해야한다는 것이다.
- 예를 들어
layoutSubviews()
를 재정의할 경우, 해당 View의 frame 설정해야 할 수 있다.
- 운 좋게도 이를 위한
translatesAutoresizingMaskIntoConstraints
가 존재한다.
translatesAutoresizingMaskIntoConstraints
TL;DR
- frame을 직접적으로 설정할 때 이 flag를 사용하라.
- Cosntraints를 사용할 때 이 flag를 false로 하여라.
- Legacy Layout 시스템에서와 같은 방식으로 작동하지만 AutoLayout 세계에서 작동한다.
- 만일 이 flag가 true이며 frame을 설정하게 될 경우, framework은 Layout Engine에서 frame을 강제하는 constraints를 생성한다.
- 이는 즉, 원하는 만큼 frame을 설정할 수 있으며 AutoLayout을 사용하여 view의 위치를 유지할 수 있다는 것이다.
- Constraints는
autoresizingMask
의 행동을 구현한다.
- Cosntraints를 사용하여 이 view와 관련된 다른 view들을 배치할 수 있다.
- Constraints를 사용할 때에는
view.translatesAutoresizingMaskIntoConstraints = false
로 하여라.
- 프로그래밍방식으로 생성된 view의 경우 기본값은 true이다.
Constraint Creation
b.topAnchor.constraintEqualToAnchor(view.topAnchor, constant:10)
- Cosntraint의 생성을 위하여
NSLayoutConstraint
factory method를 사용하였지만 가독성이 떨어져, 그 대신 Layout anchor가 새로이 도입되었다.
Constraining Negative Space
- 때때로 명확하지 않은 몇 가지 종류의 Layout이 있다.
- 전통적으로, 명확하지 않은 layout은 Dummy View를 사용하여 해결하고는 하였다.
- 하지만, 모든 View에 layer가 연결된 iOS에서는 비효율적이다. 이를 위하여
LayoutGuide
가 도입되었다.
LayoutGuide
let guide = UILayoutGuide()
view.addLayoutGuide(guide)
- LayoutGuide는 단순히 Layout engine에서 사각형을 나타낸다.
- Anchor 객체를 노출하므로 constraints 생성 구문에서 작동한다.
- Layout anchor는 margin에서 사용할 수 없다.
UIView
는 layoutMarginsGuide
를 노출한다.
Debugging Your Layout
- Constraints에 indentifier를 추가하도록 하라.
- Accessibility idnetifier를 설정하면 해당 identifier가 view와 쌍을 이루는 log에 표시되므로 원하는 view를 찾을 수 있다.
- Layout Guide에 identifier를 설정할 수 있다.
constraintsAffectingLayoutForAxis
를 사용하라.
- 한 축 또는 다른 축에서 해당 view에 영향을 미치는 constraints만 알려준다.
Resolving Ambiguity
- 너무 적은 constraints 혹은 priority의 충돌로 인하여 발생할 수 있다.
po view._autolayoutTrace
- View Debugger
exerciseAmbiguityInLayout