[WWDC] Mysteries of AutoLayout, Part 2

DEV-YONG·2021년 3월 4일
0

iOS

목록 보기
1/2

The Layout Cycle

Inside the Black Box

  • Black box의 내부에서는 아래와 같이 진행된다.
    1. View, Constraint, Property, IntrinsicContentSize 등을 구성한다.
    2. Layout Engine이 계산을 한다.
    3. Layout 얻는다.

Inside the Black Box

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

  1. Layout을 변경하면 layout engine의 모든 것은 이미 변경되었지만 UI는 아직 업데이트되지 않은 상태이다.

Layout Changed

  1. 그런 다음 Layout Pass가 진행되면, UI가 실제로 변경된다.
  • UI는 실제로 Layout Engine이 생각하는 것과 일치하도록 변경된다.

UI Updated

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"가 있다.

Update Constraints

  • 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

 // NSLayoutConstraint(item:b, attribute:.Top, relatedBy:.Equal, toItem:view, attribute:.Top, multiplier:1, constant:10)
 
 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에서 사용할 수 없다.
  • UIViewlayoutMarginsGuide를 노출한다.

Debugging Your Layout

  • Constraints에 indentifier를 추가하도록 하라.
  • Accessibility idnetifier를 설정하면 해당 identifier가 view와 쌍을 이루는 log에 표시되므로 원하는 view를 찾을 수 있다.
  • Layout Guide에 identifier를 설정할 수 있다.
  • constraintsAffectingLayoutForAxis를 사용하라.
    • 한 축 또는 다른 축에서 해당 view에 영향을 미치는 constraints만 알려준다.

Resolving Ambiguity

  • 너무 적은 constraints 혹은 priority의 충돌로 인하여 발생할 수 있다.

Diagnostic tools

  • po view._autolayoutTrace
  • View Debugger
  • exerciseAmbiguityInLayout
profile
🧑🏻‍💻 iOS Developer @TOSS

0개의 댓글