View Drawing Cycle

marisol👩🏻‍💻·2022년 6월 13일
0

오늘은 View Drawing Cycle 공부를 했다
View Life Cycle은 들어봤어도 View Drawing Cycle은 처음 들어봤는데..
알고보니 그동안 잘 모르고 썼거나, 들어만 보았던 layoutIfNeeded()setNeedsDisplay() 등의 메서드의 내용이 포함된 것 같다🧐
오늘 활동학습에서 다룬 김에 정리해야지


📌 View Drawing 개념

먼저 공식문서부터 살펴보면

View drawing occurs on an as-needed basis. When a view is first shown, or when all or part of it becomes visible due to layout changes, the system asks the view to draw its contents
UIView - The View Drawing Cycle

"뷰 드로잉은 필요에 따라 발생한다. 뷰가 처음 표시되거나, 레이아웃 변경으로 인해 뷰의 전부 또는 일부가 표시될 때
시스템은 뷰에게 내용을 그리도록 요청한다."
라고 돼있다

시스템이 뷰에게 "이거 그려줘!"라고 요청하기 위해서 draw(_:) 메서드를 호출하는 것 같다
그래서 우리는 draw 메서드를 직접 호출할 일이 없고 해서는 안된다! 고 하는데, draw 메서드는 아래에서 살펴보겠다

When the actual content of your view changes, it is your responsibility to notify the system that your view needs to be redrawn.

"뷰의 실제 내용이 변경되면, 뷰를 다시 그려야함을 시스템에게 알리는 것은 우리가 해야한다"
-> 이 때 setNeedsDisplay() 메서드를 호출한다

근데 그러면 바로 Display가 되는 것은 아니고, 예약?!하는 것과 비슷한 개념인 것 같다
다음 drawing cycle 동안 view를 업데이트해야함을 시스템에게 알려준다
그래서 여러 뷰에서 setNeedsDisplay() 메서드를 호출하면 다음 drawing cycle에서 동시에 업데이트가 가능하다 🤓

근데 또 Documentation Archive를 참고했는데(Drawing and Printing Guide for iOS),
여기서는 draw(_:) 메서드를 모두 drawRect(_:) 메서드로 설명하던데..
알고보니 drawRect(_:) 이 친구는 Objective-C 메서드였다.. 애플에서 이 문서 업데이트를 2012년을 마지막으로 하지 않았..🥲

공식문서에서도 draw(_:) 메서드의 우측 상단 language를 swift에서 Objective-C로 선택하면 drawRect(_:)로 바뀌는 걸 볼 수 있었다

그리고 뷰 업데이트를 트리거할 수 있는 4가지 액션을 설명하고 있는데,

  • 부분적으로 가려졌던 다른 뷰 이동 또는 제거
  • isHidden 프로퍼티를 false로 설정하여 이전에 숨겨졌던 뷰를 다시 표시
  • 화면에서 뷰를 스크롤한 다음, 화면으로 다시 돌아옴
  • 뷰의 setNeedsDisplay() 메서드를 명시적으로 호출

실험해보니 네번째 메서드 명시적 호출 외에는 다른 액션들은 draw 메서드가 호출되지 않고 있었다ㅠㅠ😭
2012년 이후로 업데이트 되지 않은 문서라서 그런 것인지,, 혼란하다 😵‍💫

📌 draw(_:)

그럼 이 친구는 뭐길래 직접 호출하면 안되는건지..

전달받은 직사각형 내에 리시버의 이미지를 그린다.. 라고 되어 있고
파라미터로 받는 rect 내부에 뭔가를 그린다는 것 같은데..

그럼 파라미터로 받는 rect의 설명을 자세히 보면

The portion of the view’s bounds that needs to be updated. The first time your view is drawn, this rectangle is typically the entire visible bounds of your view. However, during subsequent drawing operations, the rectangle may specify only part of your view.

"업데이트 해야 하는 뷰의 경계 부분. 뷰를 처음 그릴 때는 일반적으로 뷰의 전체 가시적 경계이지만, 이후 그리기 작업 중에는 직사각형이 뷰의 일부만 지정할 수 있다"

상당한 번역체지만🥲, 의미는 이해가 되는 것 같다
맨 처음에 뷰 드로잉 사이클 설명하면서 "뷰가 처음 표시되거나, 레이아웃 변경으로 인해 뷰의 전부 또는 일부가 표시될 때 시스템은 뷰에게 내용을 그리도록 요청한다"고 했는데,
뷰가 처음 표시될 때는 rect가 뷰의 전체 가시적 경계를 나타내지만, 이후에는 rect가 뷰의 일부가 될 수 있다는 뜻인 것 같다

그리고 Discussion의 마지막에 보면 직접 호출하면 안되고, 대신 다른 메서드를 통해 호출하라는 내용이 나와있다

This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay() or setNeedsDisplay(_:) method instead.

"이 메서드는 뷰가 처음 표시되거나, 뷰의 일부를 무효화하는 이벤트가 발생할 때 호출된다. 이 메서드를 직접 호출해서는 안된다.
뷰의 일부를 무효화하여 해당 부분을 다시 그리려면 setNeedsDisplay() 메서드를 대신 호출해라."

그러니까 뷰가 업데이트되면 직접 draw(_:)를 호출하지 말고, setNeedsDisplay()를 호출하면
시스템이 그 뷰를 다음 드로잉 사이클 때 변경해줄 것이다! 라고 생각하면 될 것 같다.

📌 View Drawing Cycle

활동학습에서 view drawing cycle 관련 메서드의 호출 순서가, 아래와 같을 것이라고 가정하고 실험을 해보았다

viewWillAppear -> updateConstraints -> updateViewConstraints -> viewWillLayoutSubviews -> layoutSubviews -> viewDidLayoutSubviews -> draw -> viewDidAppear

그런데 실험 결과 다른 부분이 있었는데..
viewDidLayoutSubviewslayoutSubviews보다 더 먼저 불려버렸다...

viewDidLayoutSubviews() 메서드의 정의를 보면,

Called to notify the view controller that its view has just laid out its subviews.

뷰컨트롤러의 뷰가 서브뷰들의 배치를 끝냈다는 것을 알리기 위해 호출됨

이라고 되어 있어서 더욱 더 이해가 안갔는데.. Discussion을 읽다보니 이런 내용을 발견했다

However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout.

이 메서드는 뷰의 서브뷰들의 개별 레이아웃이 조정되었음을 나타내지 않는다. 각 서브뷰들은 자체 레이아웃을 조정할 책임이 있다.

;;;;;;

우선 먼저 생각해야 할 것은, 각 메서드를 관리하는 주체가 다르다는 것 같다.

viewDidLayoutSubviews() 메서드는 viewController이 관리하고,
layoutSubviews()는 view에서 관리하고 있다

실제로 뭔가를 그릴때 밑바탕을 먼저 그려야하는 것처럼, 뷰컨트롤러가 갖고 있는 커다란 뷰가 먼저 레이아웃을 잡아야만 그 뷰의 서브뷰도 레이아웃을 잡을 수 있는것 같다

여기서 살짝 얘기한 layoutSubviews() 메서드 또한 draw(_:) 메서드처럼 직접 호출해서는 안되는 메서드라고 한다
그래서 layoutSubviews()의 공식문서를 보면 setNeedsLayout() 메서드나 layoutIfNeeded() 메서드를 통해 간접적으로 호출하라고 나와있다

  • draw(_:) -> setNeedsDisplay()를 통해 간접 호출
  • layoutSubviews() -> setNeedsLayout()layoutIfNeeded()를 통해 간접 호출

이렇게 기억하면 될 것 같다 🤓


참고자료

0개의 댓글