Mysteries of Auto Layout, Part 2

Eddy📱·2022년 8월 16일
0

WWDC

목록 보기
2/16
post-thumbnail

Mysteries of Auto Layout, Part 2

2번째 파트입니다.

  1. The Layout Cycle

앱 실행하고 엔진 걸쳐서 레이아웃이 완성된다. 이전에 보여준 프로세스다.

사이클이 이렇게 이루어져 있다.

Run loop으로 시작해서 레이아웃 변하는것을 계산하고 그다음에 구조에서 이를 확인하고 업데이트 시킨다.

간단한 예시

먼저 constraint를 수정하면 레이아웃 엔진이 이를 알게 되고 이미 이곳에서 변한것을 알게되지만 UI에서 업데이트가 아직 안된다.
하지만 그 이후에 매치되어서 맞는 곳을 업데이트 시킨다.

constraints를 바꾸게하게 되면 무슨 일이 일어날까?

  1. constraints 변경
  2. 레이아웃 엔진이 다시 계산을 시작한다.(즉 특정 뷰의 origin, size 다시 확인하게된다.)
  3. 그리고 다시 계산할 때 이 변수들이 다시 새로운 값이 나올 것이다.
  4. 그래서 superview에게 layout 변화가 필요하다고 알려준다.
  5. 이는 Deferred layout pass를 일으킨다.

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

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

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

💡 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:티스토리

하지만 이렇게 부르지않고 되는 경우가 있다.

  1. 초기의 constraints 세팅은 interface builder안에서 이상적으로 이루어져 있다.
  2. 아니면 viewDidLoad와 같은 곳에서 호출되어서 코드적으로 할당되는 경우도 있다.

그래서 주기적으로 layout update가 이루어진다.

그리고 만약 constraint 변화가 즉시 필요할 때가 있다.

반면에 로직때문에 코드에 의해 반복으로 분리된 메서드로 인해 나중에 호출되는 경우가 있다.

이렇게 하면 유지하고 다른사람이 이해하기 어렵다.
그러면 어디에서 constraints를 업데이트 하는 것을 구현하는 것이 좋을까?

constraints 바꾸는 곳이 너무 느려서 문제가 생길 수 있는데, constraints 바꾸는 것을 constraints 업데이트 하는 내부에서 이루어지면 훨씬 빠르게 된다.

이유: 모든 것을 한번에 다루기 떄문에 더 빠른 것이다.

를 들어, activate, deactivate를 array 한번에 하는 것이 하나씩 일일이 하는 것보다 더 빠른 것과 같은 의미다.

그리고 만약에 뷰의 구조를 바꾸어야할 때 하나씩 rebuild를 하게 된다면 이는 매우 많은 시간을 낭비하는 것이다.

그래서 이렇게 하지말고 setNeedsUpdateConstraints를 통해 rebuild가 필요한 부분 모두를 한번에 하게 된다면 위와 다르게 시간 낭비가 줄어들 것이다. 하나씩 하지 않고 한번에 변화가 필요한 것을 한번에 해주기 떄문이다

layoutSubviews() 메서드는 reposition이 필요할 때 슈퍼뷰를 호출하는데, 이는 변화가 필요한 것 하나씩을 호출하는 것이 아니라 슈퍼뷰를 호출해서 변화가 필요한 곳을 한번에 업데이트 시켜준다.

그래서 layout engin를 통해 subview frames를 복사한 다음 setFrame를 통해 해준다.

그리고 커스텀 layout를 위해 override한 layoutSubViews()를 호출하는 것을 조심해야한다.

구현하기 쉽지만 문제 생기기 쉽다. 일부는 layout update가 되어있고 일부는 되어있지 않는 경우가 있다.

그 순간이 문제가 될 것이다.

그래서 이 문제를 해결하기 위해서는 규칙을 따라야 한다.

Do 1: super.layoutSubviews()를 호출해야한다.

Do 2: subtree내에서 invalidate layout를 하는 것은 괜찮다. 하지만 이것은 superview 구현을 부르기 전에 이루어져야한다.

Don't 1: setNeedsUpdateConstraints() 메서드를 부르면 안된다.
update constraints pass가 있었는데 이걸 끝내고 만약 놓친게 있으면 이미 끝난거라 어쩔수없다.

Don't 2,3: 바깥의 subtree에 있는 것을 invalidate layout를 하면 안된다. 이렇게 하게되면 쉽게 layout feedback loops를 일으킬 수 있다. 이는 layout를 수행하는 행동에서 실제로 레이아웃을 다시 지저분하게 만든다.
이렇게 하면 무한 반복이 이루어질 수 있다.

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

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

1. Interacting with 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끼리 제약사항 설정하려고 할 때)

1. Constraint Creation

Constraint를 만드는 것을 알려준다.

이렇게 하게 되면 장황하다. 그리고 읽기도 어렵다. 모두 다 읽어야 어떻게 동작하는지 알 수 있다.

이제 이를 변하게 더 쉽게 할 수 있다.

설명은 필요없죠..? 다들 아시죠!!!

이렇게 하게 되면 장점은 compile time때 레이아웃 에러가 나는지 체크해줄 수 있다. 그래서 runtime보다 일찍 확인할 수 있고 문제를 빠르게 파악할 수 있다.

또한 가독성도 좋고 유지관리하기도 더 좋아진 코드인 셈이다.

2. Constraining Negative Space

여기에서 두가지 문제가 존재한다.

여기에서 생기는 문제를 해결하려고 한다.

그림을 보면 Dummy View를 넣어서 해결할 수 있다.

empty view를 넣고 버튼 사이의 공간을 채울 수 있다. 그리고 동일한 width를 넣어서 window가 resize할 때에도 사이즈가 동일하게 남도록 할 수 있다.

아래의 케이스도 동일하다.

empty view를 활용해서 이것의 edges를 label, image에 constraints하고 그리고 나서 가운데에 놓도록 한다.

이렇게 해서 문제를 해결할 수 있지만 조금 trick 같은 느낌이 들지않는가?

이를 해결하기 위해서 다른 방식을 제공한다.

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

해결: layoutGuide/ layoutMarginGuide

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

예시 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 공식문서

Debugging Your Layout

디버깅 레이아웃한다. 이런 경험 다들 해본적 있을 것이다.

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

그리고 콘솔에도 아래처럼 레이아웃 에러가 나는 것을 볼 수 있다

  1. Unsatisfiable Constraints

이제 로그를 분석해본다.

먼저 이 로그를 보면 broken된 것을 보여준다.

하나의 엔진이 break되어서 레이아웃을 해결해야 한다.

토성의 width가 엔진에서 broken 된 것을 확인할 수 있다.

이곳 외에도 위의 로그에서도 문제가 발생했다는 것을 표현해주고 있다.

이제 그 외에 다른 로그에서도 문제가 발생하는 것을 볼 수 있는데 이는 다 연결되어있다.

image의 수평과 leading, trailing 문제

그리도 수직 layout 문제와 아래의 라벨과의 수직 layout의 문제가 있다.

그리고 문제가 토성이 100이상 tall한데 슈퍼뷰로부터 top까지의 길이가 그만큼이 되지 않기 때문에 문제가 되었고 이로 인해 constraints가 붕괴되었다.

이렇게 보면 사실 어떤것인지 파악하기 어렵기도 하다.

그래서 만약 identifier를 constraints에 넣는다면 더 보기 편하다.

ientifier를 넣는 방법은 3가지가 있다.

  • constraint identifer를 명확하게 추가한다.
  • VFL를 사용해서 한다.
  • interfaceBuilder를 활용해서 넣어준다

데모

아까 레이아웃이 문제가 있는 것을 이렇게 디버그를 통해서 찾아낼 수 있다.

로그에서 아까처럼 어디에서 에러가 나느지 확인할 수 있다

여기에서 나는것 하나씩 찾아가서 확인해서 고치면 제대로 나온다!

왜 Break 나는지에 대해 체크하는 법

이것들을 확인해서 어디에서 문제가 있는지 확인하는게 좋다.

Resolving Ambiguity

첫 번째로는 constraints가 너무 적게 되어있어서 토성의 위치가 어디로 가야할지 모른다.

그래서 중앙에 오지 않고 왼쪽이나 오른쪽으로 갈 수 있다

두 번째로는 우선순위 충돌이 일어날 수 있다.

우선순위 제대로 안정하면 막 늘어난다.

그래서 이를 설정해주어야한다.

우선순위 정해서 answer이 길어지지 않도록 한다.

반대로 우선순위를 바꾸면 answer이 길어질 수도 있따. 이는 기획에 따라 원하는 방향으로 설정해주면 된다.

이를 해결하기 위한 툴은

1번째로는

요것들 아시죠? 노랑색과 빨강색이 뜨는데 이게 레이아웃이 애매할 떄 뜨는 것이다.

이 로그를 확인해서 해결해줄 수 있다.

이렇게 나온다!!

그래서 이를 보고 어디가 애매한지 체크하고 해결할 수 있다.

2번째로는

_autolayoutTrace로 이제 디버그에서 po를 활용해서 확인해본다.

3번째로는

Select Debug > View Debugging 으로 하면 뷰 각각의 사이즈를 볼 수 있다.

이것을 통해 뷰의 사이즈가 얼만큼인지 확인할 수 있다

그리고 이거 보았을 텐데! 이게 바로 뷰 디버거 를 확인하는 곳이다.

뷰 구조를 볼 수 있다!!! 어떻게 레이아웃이 잡혀있는지 3D로 확인할 수 있다.

4번쨰로는 exerciseAmbiguittyLayout를 통해 확인할 수 있다.

Demo

1번쨰 방식으로 디버거를 검색한다

잘 보시면 Saturn에서 Ambiguous하다는 것을 볼 수 있다.

또한 po에 애매한지 체크해볼 수 도 있다

exercise를 활용해서도 확인이 가능하다

이제 실제로 그곳에 가서 애매한 것을 체크해서 가서 해결하면 끝난다!

디버그 관련 Summary

profile
Make a better world

0개의 댓글