iOS의 레이아웃 사이클

업데이트:

UIKit을 기준으로 iOS의 레이아웃에 대해 설명하고 관련 UIViewController와 UIView의 메서드를 알아보는 글입니다

iOS의 레이아웃

모바일기기는 다양한 해상도를 지원하기 때문에 모바일 앱을 개발하는데에 있어 레이아웃을 조정하는 일은 아주 중요합니다. iOS에서는 다음 3가지 주요 접근 방식을 사용해 레이아웃을 조정합니다.

  1. Frame 기반의 프로그래밍 방식(Frame-Based Layout)
  2. Autoresizing masks
  3. Auto Layout

위 방식들은 다음 링크에 잘 설명되어 있습니다.

레이아웃 사이클

레이아웃 사이클은 세가지 단계로 이루어집니다.

  1. 제약 조건(Constraints)
    • 오토레이아웃의 제약 조건을 갱신합니다.
    • 제약 조건은 뷰를 실제로 배치하는데에는 영향을 주지 않습니다.
    • 제약 조건의 갱신은 뷰 계층구조에서 하위뷰에서 상위뷰의 순서로 이루어집니다.
  2. 레이아웃(Layout)
    • 제약 조건을 바탕으로 레이아웃을 갱신합니다.
    • 레이아웃은 구체적인 뷰의 frame입니다.
    • 이 단계에서 뷰의 Center와 Bounds를 결정합니다.
    • 레이아웃의 갱신은 뷰 계층구조에서 상위뷰에서 하위뷰의 순서로 이루어집니다.
  3. 그리기(Draw)
    • 레이아웃단계에서 구한 frame을 CoreGraphics를 사용하여 화면에 그립니다.

아래 사진의 순서대로 ViewController와 ViewController의 view의 각 메소드가 호출됩니다.

UIView

제약 조건(Constraints)

updateConstraints

뷰의 제약 조건을 갱신합니다. 이 메서드를 재정의하면 제약 조건의 변경 사항을 최적화할 수 있습니다. 효율적인 앱을 만드려면 변경이 필요한 항목만 변경해야합니다.

뷰에 영향을 미치는 변화가 발생한 후 즉시 제약조건을 갱신하는 것이 거의 항상 더 깔끔하고 쉽습니다. 예를 들어 버튼을 눌러 제약 조건을 변경하려면 버튼을 눌렀을 때 호출되는 함수에서 직접 변경하는게 낫습니다. 제약 조건을 변경하는 것이 느리거나 여러 번의 쓸모없는 변화가 발생하는 경우에만 이 함수를 재정의해야 합니다.

제약 조건의 갱신은 뷰 계층구조에서 하위뷰에서 상위뷰의 순서로 이루어지기 때문에 상위뷰의 updateConstraints 메서드는 가장 마지막에 호출해야 합니다.

이 함수는 직접 호출하면 안됩니다. 시스템이 적당한 타이밍에 호출해줍니다. 시스템에 제약 조건의 갱신을 요청하기 위해서는 setNeedsUpdateConstraints 메서드를 호출해야합니다.

setNeedsUpdateConstraints 메서드를 호출하면 뷰의 제약조건을 갱신해야한다는 플래그를 표시하게 됩니다. 시스템은 실행루프가 끝나고 다음 실행루프를 실행할 때 뷰의 플래그를 보고 한번에 모든 제약 조건를 갱신합니다. 따라서 호출하는 순간에 제약 조건이 바로 갱신되는 것이 아니라 다음 실행루프에 한번만 갱신하게됩니다. 한 번의 실행루프에서 갱신이 여러 번 일어나면 비효율적이기 때문입니다.

개발자가 플래그를 표시하지 않아도 시스템이 플래그를 표시하는 경우는 대표적으로 다음과 같습니다. 이 경우에도 뷰는 다음 실행루프에서 갱신됩니다.

  1. 뷰의 frame 변경
  2. 뷰에 하위뷰가 추가되는 경우
  3. 디바이스의 orientation이 변경되는 경우(디바이스 회전)
  4. Constraint Constant의 변경

updateConstraintsIfNeeded

시스템은 제약의 갱신이 필요할 때 이 메서드를 호출하여 뷰와 하위 뷰의 제약조건이 갱신되도록 합니다. 새로운 제약 조건을 바로 적용해야할 경우 개발자가 수동으로 호출할 수 있습니다. 이 메서드는 다음 실행루프가 아닌, 호출된 순간에 제약조건을 갱신합니다.

이 메서드는 재정의하면 안됩니다.

레이아웃(Layout)

layoutSubviews

하위 뷰를 배치합니다. 하위 클래스는 하위 뷰의 더 정확한 레이아웃을 조정하기 위해 필요한 경우 이 메서드를 재정의할 수 있습니다. 하위 뷰의 오토리사이징 및 오토레이아웃이 원하는 동작을 제공하지 않는 경우에만 이 방법을 재정의해야 합니다. 이 메서드에서 하위뷰의 frame을 직접 설정할 수 있습니다.

레이아웃의 갱신은 뷰 계층구조에서 상위뷰에서 하위뷰의 순서로 이루어지기 때문에 상위뷰의 layoutSubviews 메서드는 가장 먼저 호출해야 합니다.

이 함수는 직접 호출하면 안됩니다. updateConstraints 메서드와 같이 강제로 레이아웃을 갱신하고 싶을 경우 setNeedsLayout메서드를 호출하면 다음 실행루프에서 레이아웃이 갱신됩니다. 즉시 레이아웃을 갱신하고 싶을 때는 layoutIfNeeded 메서드를 호출할 수 있습니다.

오토레이아웃을 사용할 때, 레이아웃 엔진은 제약 조건의 변화를 만족시키기 위해 필요에 따라 뷰의 위치를 갱신합니다.

requiresConstraintBasedLayout

뷰의 레이아웃이 제약 조건에 의존하는지 여부를 나타내는 Bool타입 연산 프로퍼티입니다. 뷰가 제약 조건 기반 레이아웃을 사용하고 제대로 작동할 경우 true를 반환합니다.

커스텀 뷰이고, 오토리사이징을 사용하여 올바른 레이아웃을 조정할 수 없는 경우 이 프로퍼티를 true로 재정의해야 합니다.

그리기(Draw)

draw

Core Graphics 및 UIKit 같은 기술을 사용하여 뷰의 내용을 그리는 하위 클래스는 이 메서드를 재정의하고 해당 드로잉 코드를 구현해야 합니다. 이 외의는 메서드를 재정의 할 필요가 없습니다. 예를들어, 뷰가 배경색만 표시하거나, 기본 레이어를 사용하여 직접 컨텐츠를 설정하는 경우에는 재정의 할 필요가 없습니다. 이 메서드는 뷰를 다시 그리는데만 사용되어야 하며, 레이아웃을 조정하거나 데이터를 조작하면 안됩니다.

이 메서드는 뷰가 처음 표시될 때, 또는 뷰의 보이는 부분을 무효화하는 이벤트가 발생될 때 호출됩니다. 직접 이 메서드를 호출하면 안됩니다. 뷰의 일부분을 무효화하고 다시 그려지게 하려기 위해서는setNeedsDisplay 메서드를 호출할 수 있습니다.

뷰가 갱신되는 상황은 다음과 같습니다.

  1. 뷰를 부분적으로 가리고 있던 다른 뷰 이동 또는 제거
  2. hidden 프로퍼티를 No로 설정하여, 이전에 숨겨진 뷰를 다시 볼 수 있도록 설정
  3. 뷰를 화면 밖으로 스크롤한다음, 다시 화면으로 이동
  4. 뷰의 setNeedsDisplay 또는 setNeedsDisplayInRect 메서드를 명시적으로 호출

드로잉 사이클

뷰가 처음 화면에 나타나면, 시스템은 컨텐츠를 그려달라고 요청합니다. 시스템은 이 컨텐츠의 스냅샷을 캡쳐하고, 해당 스냅샷을 뷰의 시각적 표현으로 사용합니다. 뷰의 내용을 변경하지 않으면 draw 메서드를 다시 호출 할 수 없습니다. 스냅샷 이미지는 뷰와 관련된 대부분의 작업에 재사용됩니다.

뷰의 내용이 변경되면 draw 메서드를 직접 호출하지 않습니다. 대신 setNeedsDisplay 메서드를 호출하여 뷰를 무효화합니다. 이 메서드는 뷰의 내용이 변경되어 다음 사이클에 다시 그려질 필요가 있다고 시스템에 알립니다. 뷰를 갱신하기 위해 다음 드로잉 사이클까지 기다리기 때문에, 여러 뷰에서 이 메소드를 호출하여 동시에 업데이트 할 수 있습니다.

사이클 단계는 다음과 같습니다.

  1. 현재 뷰의 스냅샷 캡쳐
  2. 컨텐츠가 변경되면 setNeedsDisplay 메서드를 호출해 시스템에게 갱신 요청
  3. 다음 드로잉 사이클이 오면, 갱신 요청받은 뷰를 전부 갱신

UIViewController

제약 조건(Constraints)

updateViewConstraints

뷰컨트롤러의 뷰가 제약 조건 갱신이 필요할 때 호출됩니다. 이 외의 설명은 UIView의 updateConstraints에서 설명한 것과 같습니다.

제약 조건의 갱신은 뷰 계층구조에서 하위뷰에서 상위뷰의 순서로 이루어지기 때문에 상위뷰의 updateViewConstraints 메서드는 가장 마지막에 호출해야 합니다.

레이아웃(Layout)

viewWillLayoutSubviews

뷰 컨트롤러의 뷰가 하위뷰의 레이아웃을 조정하려고 함을 알리기 위해 호출됩니다. 뷰의 bounds가 변경될 때, 하위 뷰의 위치를 조정합니다. 하위뷰의 레이아웃을 조정하기 전에 무언가 변경하기위해 뷰컨트롤러에 이 메서드를 재정의할 수 있습니다. 기본적으로 이 메서드에는 아무런 구현이 되어있지 않기 때문에 호출해도 어떤 일도 발생하지 않습니다.

viewDidLayoutSubviews

뷰 컨트롤러의 뷰가 방금 하위뷰의 레이아웃을 조정했음을 알리기 위해 호출됩니다. 뷰의 bounds가 변경될 때, 하위뷰의 위치를 조정합니다. 하지만 이 메서드는 하위뷰의 개별 레이아웃이 완벽하게 조정되었을 때 호출되지는 않습니다. 따라서 각 하위뷰는 자신의 레이아웃을 조정할 책임이 있습니다.

하위 뷰의 레이아웃이 모두 조정된 후에 무언가 변경하기위해 뷰컨트롤러에 이 메서드를 재정의할 수 있습니다. 기본적으로 이 메서드에는 아무런 구현이 되어있지 않기 때문에 호출해도 어떤 일도 발생하지 않습니다.

UIViewd의 레이아웃 사이클 메서드

사이클 업데이트 플래그 트리거
Constraints updateConstraints setNeedUpdateConstraints updateConstraintsIfNeeded
Layout layoutSubviews setNeedsLayout layoutIfNeeded
Draw draw setNeedDisplay, setNeedDisplayInRect 없음

출처

애플 공식 개발자 문서

https://jinshine.github.io/2018/06/07/iOS/오토레이아웃(AutoLayout)과%20Layout%20개념/

https://itpeace.tistory.com/44

https://zeddios.tistory.com/359

https://jcsoohwancho.github.io/2020-02-17-View-업데이트/

댓글남기기