[iOS] Mysteries of Auto Layout, Part 2

mmim·2022년 8월 15일
0

Mysteries of Auto Layout, Part 2

Mysteries of Auto Layout, Part 2

🪄 Mystery 7: The Layout Cycle

Black box의 내부에서는 아래와 같이 진행된다.

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

ZeddiOS:티스토리

Deferred Layout Pass

프레임이 레이아웃 엔진에서 변하면 구조에서는 안바뀌어서 아직 위치가 재조정되어 있지 않다.

이때 deferred layout pass를 통해 위치가 재조정되며 이것이 끝나게 되면 잘못 위치한 것을 올바른 곳으로 옮겨지게 된다.

pass1: 뷰는 메서드를(superview.setNeedsLayout()) 통해 요청을 한다.
pass2: 보통 next layout pass에 메서드가 호출되며 뷰의 레이아웃이 업데이트된다.

  • 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를 수정하지 말아라.

참고문헌

결론적으로 Layout Cycle에서 기억해야할 부분이 있다.

1️⃣ 즉시 모든 프레임이 변할 것이라고 예측하면 안된다. constraints가 변하게 되면 과정을 걸쳐서 레이아웃이 변하게 되는 것이다. 그래서 즉시 변하지 않는 다는 것을 알아야한다!
2️⃣ layoutSubviews()를 override해서 사용하면 layout feedback loop에 빠질 수 있으니 조심해야한다.

🪄 Mystery 8: Legacy Layout

원래는 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 세팅 -> autoresizingMask(superview size바뀌면 어떻게 하위 뷰의 사이즈를 바꿀지 결정) -> autoLayout(constraints 이용!)(layoutSubviews 오버라이딩 할 때는 frame 설정 가능)
  • translateAutoresizingMaskIntoContraints를 false해주기!

코드로 작성할 때 frame을 직접 설정하는 경우, translateAutoresizingMaskIntoContraints를 false로 주지 않으면 resizingMask와 contraints 세팅한 것이 충돌나서 제대로 레이아웃이 세팅되지 않는다!

  • anchor 등장

anchor는 factory method로 이전보다 읽기 편해짐
컴파일 에러로 알려줌(ex. location과 size끼리 제약사항 설정하려고 할 때)

🪄 Mystery 10:Contraining Negative Space

이전의 비효율적인 레이아웃 구성: dummy view 사용

‼️ 해결: layoutGuide / layoutMarginGuide

새로운 방식의 layout guide class 제공한다.

layoutGuide / layoutMarginGuide

예시 1:layout guide가 있다.

예시 2: layoutMarginGuide라는 것이 생기게 되었다.

이것들을 사용해서 레이아웃 에러를 해결할 수 있다.

이전의 방식보다 더 가볍고 뷰들의 뷰 구조를 망가트리지 않고도 레이아웃을 해줄 수 있다.

dummy View의 문제점은 뷰의 구조에 포함된다. 왜냐하면 empty View이기 떄문에 결국 다른 뷰와 동일하게 추가해야하기 떄문이다.

이는 결국 다른 뷰처럼 overhead에 추가되는 단점이 존재한다.

또한 다른 뷰들과 같이 있기 떄문에 가끔 message를 방해해서 문제가 생기면 어느 부분에서 생긴 것인지 파악이 어렵다.

그래서 나온 것이 LayoutGuide, layoutMarginGuide이다.

  • Layout guides 장점
    1: a lightweight method for encapsulating
    2: separate the layout

layout guide 공식문서

debug layout

이런 경험 다들 해본적 있을 것이다.

왼쪽처럼 디자인했는데 실제로 실행해보면 오른쪽 처럼 나오는 것을 볼 수 있었을 것이다.

코드로 설정하는 법

명시적으로 identifier를 작성하면 레이아웃 모호하다고 에러 생겼을 때 확인 용이

IB에서 설정하는 법

contraintsAffectingLayoutForAxis 로 디버깅하기
➡️ 한 축 또는 다른 축에서 해당 view에 영향을 미치는 constraints만 알려준다.

resolve ambiguity

언제 모호한 상황이 생길까?
-> 레이아웃 부족 or 너무 많을 때(conflict)

  • 해결법들

=> size 문제인지 location 문제인지 확인 가능

=> lldb 로 solution에 대한 힌트를 제안받을 수 있음

디버그 관련 Summary

profile
예비 iOS 개발자의 기록

0개의 댓글