2 minute read

iOS에서 레이아웃을 접근하는 방법 3가지

iOS에서 레이아웃을 구성할 때 접근하는 방법이 3가지가 있다.

  • Frame 기반의 프로그래밍 방식

    • 가장 유연하고 성능 빠름
    • 모든 뷰에 개별적인 설정과 관리 필요
    • 설계 및 디버그나 유지관리에 많은 노력 필요
  • Autoresizing masks

    • Frame 기반에서 한층 성장한 방법
    • 복잡한 UI를 구현하기 어려워 AutoLayout 도입
  • AutoLayout

    • 제약조건을 이용해 유저 인터페이스 정의
    • 뷰간의 관계 설정을 통해 크기와 위치 계산
    • Frame기반보다 느린 퍼포먼스

View Layout Cycle

Layout을 구성하는 다양한 방법은 위에서 본 바와 같다. 위의 3가지로 Layout을 구성할 수 있는데 iPhoneX 시리즈의 노치디자인에 대응하기 위해선 Frame 기반보다 AutoLayout 기반이 더욱 효율적이다. 그렇다면 이미 Frame 기반으로 배포된 앱을 AutoLayout으로 변경할 때 뷰 레이아웃 사이클이 필요하다.

View를 그리는 3가지 단계

  1. Constraint Update
    • AutoLayout의 제약을 갱신한다
    • subview -> superview 순서대로 호출된다
  2. Layout Update
    • 구체적인 뷰의 수치값 업데이트
    • View의 Center, Bounds를 결정한다
    • superview -> subview 순서대로 호출된다
  3. Render
    • UIView의 drawRect:가 호출되면 뷰를 그린다

Constraint

  • 업데이트 :updateConstraints

    보통 View의 Constraints를 동적으로 변경할 때 구현한다.

    subview에서 superview 순서로 호출되기 때문에 super.updateConstraints는 가장 마지막에 호출된다 만약 뷰의 제약을 갱신하고 싶다고해도 코드로 직접 호출해선 안된다. 시스템이 적절한 타이밍에 호출해주기 때문이다.

    시스템에 뷰의 제약 갱신을 요청하기 위해 setNeedsUpdateConstraints 라는 메소드를 호출할 수 있다 이 함수 호출을 통해 제약의 갱신을 해야한다는 플래그를 표시한다 그 플래그를 보고 시스템이 처리하게된다.

    • 플래그를 심는다는 것은 한번의 RunLoop가 끝나고 다음 RunLoop를 실행할 때 View의 플래그를 보고 한번에 모든 View를 갱신하는 방법을 뜻한다.
  • 플래그 : setNeedsUpdateConstraints

    이 메소드를 실행하게 되면 갱신을 하지 않고 시스템이 한번에 갱신을 실행한다 updateConstraints를 호출하기 위해 표시해두는 의미로 기억해두자.

  • 트리거 :updateConstraintsIfNeeded

    이 메소드는 시스템이 정리해서 제약의 변경을 실행할 때 호출된다 직접 호출도 가능하다.

Layout

  • 업데이트 : layoutSubviews

    하위 뷰들을 배치한다. 현재 뷰와 모든 자식 뷰의 크기와 위치를 제공한다. 이 메소드는 frame을 다시 계산해야할 때 호출하기 때문에 오버라이딩을 해서 조절할 수 있지만 직접 호출하는 것은 금지되어있다. 대신 시스템에 요청하기 위해 여러 방식들이 존재한다. 우리는 레이아웃의 크기나 위치와 연관된 로직을 layoutSubviews가 완료될 때 viewDidLayoutSubviews에 호출해야한다.

  • 플래그 : setNeedsLayout

    시스템에 요청하는 여러 방식 중 하나이다. layoutSubViews를 가장 적은 부하로 호출할 수 있다 강제로 레이아웃을 갱신하고 싶다면 다음 RunLoop에 갱신이 된다.

  • 트리거 : layoutIfNeeded

    setNeedsLayout과 달리 즉시 layoutSubviews를 호출할 수 있다 하지만 호출했을 때 View가 재조정되어야하는 이유가 없다면 layoutSubviews는 호출되지다 않는 만약 우리가 동일한 RunLoop에서 레이아웃의 업데이트 없이 layoutIfNeeded를 두 번 호출했다면, 두 번째 호출은 layoutSubviews를 발생시키지 않는다. 애니메이션하는 상황에서 특히 유용하다

Draw

  • UIView의 인스턴스 메소드로 UIView 객체 인스턴스로부터 접근해서 사용하는 메소드이다 rect라는 파라미터를 받아 업데이트 해야하는 영역을 갖는다 Core Graphics, UIkit 등에서 뷰의 컨텐츠를 그리는 기술을 사용하는 하위뷰들은 해당 메소드를 오버라이드해야하며 해당 구현부에 그리기 코드를 작성해야한다

  • 대략 대충 구현을 하자면…

class TestView: UIView {
  var numberOfPoints = 0 {
    didSet {
      setNeedsDisplay() //numberOfPoints가 변하면 뷰를 그리는게 달라지기 때문에 추가함
    }
  }
  
  override func draw(_ rect: CGRect) {
    switch numberOfPoints {
      case 0:
      	return
      case 1:
      	drawLine(rect)
      case 2:
      	drawTriangle(rect)
     default:
      	drawPoint(rect)
    }
  }
}
  • 직접호출해선 안된다 뷰의 일부분이 다시 그려져야한다면 setNeedsDisplay 로 시스템에 알려줄 수 있다

출처

  • https://daeun28.github.io/이론/post22/#제약-조건constraints
  • https://jinshine.github.io/2018/06/07/iOS/오토레이아웃(AutoLayout)과%20Layout%20개념/
  • http://monibu1548.github.io/2019/02/08/layout-cycle/