[iOS] View의 Display & Layout 관련 메소드

3 분 소요

iOS를 개발하면서 한번 이상은 사용해본 것 같지만 정확한 의미를 잘 모르고 사용했던.. 뷰 레이아웃 관련 메소드들을 이번 기회에 정리 해보려고 한다.

layoutSubViews()

layoutSubViews를 가장 먼저 살펴보자. 개발문서를 확인해보면

가장 눈에 띄는건 subview들을 배치한다, 직접 호출하면 안된다! 이다.

UIViewSubclass에서 이 메소드를 override하여 사용하면 subview들의 레이아웃을 내 입맛에 맞게 구성 할 수 있다. 상위 뷰의 bound에 변화가 있을때 마다 subview들의 크기+위치가 어떻게 변경될지 정의를 할 수 있는것 같다.

+++ 다른 자료들을 찾아보면

superview의 크기의 변화가 발생(auto layout에 의해)한 경우 autoresizingMask 프로퍼티를 설정하여 어떻게 대응할지 정의하고 subview들에게 auto resizing동작을 적용하여 subview들에게 연쇄적으로 영향을 준다

고 하는데 한 줄에 모르는 단어가 너무 많다ㅠㅠ 나중에 autoresizingMask에 대해 공부를 다시 해보자..!!

다시 개발문서의 제일 아래부분을 보면

setNeedsLayoutlayoutIfNeeded에 대한 언급이 있다. setNeedsLayoutlayoutIfNeeded를 호출하여 간접적으로 layoutSubViews를 호출하고 subview들의 레이아웃을 설정한다고 나와있다.

그럼 뷰 컨트롤러가 subview들의 레이아웃을 구성하는건 setNeedsLayoutlayoutIfNeeded를 호출 했을때만이냐 한다면 아니다

크게 다음의 이벤트 경우에 레이아웃 구성 변경이 발생 된다고 한다.

  • 뷰의 bound 크기가 변경된 경우
  • 세로모드, 가로모드에 의해 뷰가 변화된 경우
  • Core Animation sublayers가 세팅된 경우(뷰의 layer가 변경되거나 레이아웃을 요청한 경우)
  • UIView의 setNeedsLayout 또는 layoutIfNeeded 가 호출된 경우
  • UIView의 layer에서 setNeedsLayout 이 호출된 경우

결론적으로! 위의 경우에 의해 레이아웃 구성이 변경되려는 경우 layoutSubViews가 호출된다는 것 같다.

또 우리에게 익숙한 UIViewControllerLife CycleviewWillAppearviewDidAppear 와 비슷하게 생긴 viewWillLayoutSubviewsviewDidLayoutSubviews 메소드가 UIViewController 존재하는데

실행 순서대로 정의와 용도를 설명해보면 다음과 같다고 한다.

  1. viewWillLayoutSubviews()
    • 뷰 컨트롤러에게 하위뷰 들의 레이아웃을 구성한다고 알리기 위해 사용하는 메소드
    • 용도: 뷰의 크기나 위치가 변하면 subview들의 크기나 위치도 똑같이 변할텐데, 해당 레이아웃들이 결정되기 전에 수행하고 싶은 작업들을 작성
    • 예시: 뷰들을 추가/제거, 뷰들의 크기/위치 업데이트, constraint 업데이트, 뷰와 관련된 property 업데이트
  2. layoutSubViews()
    • 위에서 설명했죵?
  3. viewDidLayoutSubviews()
    • 레이아웃이 결정된 후 호출
    • 용도: subview들의 크기나 위치를 최종적으로 업데이트 하고 싶을때, table viewcollection view의 데이터를 reload 할 때

요약해 보면

layoutSubviews는 view의 레이아웃 구성 변경을 호출한 즉시 실행되는 메소드 (++호출 되면 해당 view의 모든 subview들도 layoutSubviews 도 연달아 호출한다)

그리고 view의 레이아웃 구성 변경이 호출 되는 시점은 크게

  1. view의 크기를 조절할 때
  2. subview를 추가할 때
  3. 사용자가 UIScrollView를 스크롤할 때
  4. 디바이스를 회전시켰을 때 (Portrait, Landscape)
  5. view의 Auto Layout contraint 값을 변경시켰을 때

이렇게 있는데 layoutSubviewsupdate cycle에서 호출 되게끔 자동으로 예약해주는 상황들이다.

모르는 단어가 또 나왔다 update cycle은 뭘까??

설명에 앞서 Main Run Loop에 대해서 먼저 짚고 넘어가자

Main Run Loop 어플리케이션이 실행되면 iOS의 UIApplication이 매인 스레드에서 Main run loop를 실행시킵니다. Main run loop는 돌아가면서 터치 이벤트, 위치의 변화, 디바이스의 회전 등의 각종 이벤트들을 처리하게 됩니다. 이러한 처리 과정을 update cycle이라 하고 각 이벤트들에 알맞는 핸들러를 찾아 그들에게 처리 권한을 위임하며 진행됩니다.

버튼의 터치 이벤트를 @IBAction 메소드가 처리하는 것을 떠올리시면 될것 같습니다. 버튼의 터치가 발생하면 @IBAction 메소드안에 있는 이벤트들이 우선적으로 처리됩니다. 이렇게 발생한 이벤트들을 모두 처리하고 권한이 다시 Main run loop로 돌아오게 되고 이 시점을 update cycle이라고 합니다.

Update Cycle Main run loop에서 이벤트가 처리되는 과정에서 버튼을 누르면 크기나 위치가 이동하는 애니메이션과 같이 개발자의 의도에 따라 layout이나 position 값을 바꾸는 핸들러가 실행될 때도 있습니다. 이러한 변화는 개발자의 trigger에 즉각적으로 반영되는 것이 아닙니다.

시스템은 이러한 layout이나 position이 변화되는 view를 체크합니다. 그리고 모든 핸들러가 종료되고 Main run loop로 권한이 다시 돌아오는 시점인 update cycle에서 이런 View들의 position이나 layout의 변화를 적용시킵니다.

즉, postion이나 layout 값을 변경하는 코드와 실제로 변경된 값이 반영되는 시점에는 시간차가 존재한다는 뜻입니다.

setNeedsLayout()

layout이나 position 변화를 자동이 아니라 수동으로 예약 할 수 있는 메소드가 setNeedsLayout이다 이 메소드를 호출한 view는 layout, position이 재계산되어야 하는 view라고 수동으로 체크가 되며 update cycle에서 layoutSubviews가 호출되게 됩니다. 이 메소드는 비동기적으로 작동하기 때문에 호출되고 바로 반환됩니다. 그리고 view의 변화된 모습은 update cycle에 들어갔을 때 바뀌게 됩니다.

setNeedsDisplay()

해당 view 인스턴스가 다음 drawing cycle에서 다시 그려져야 한다는걸 알린다 drawing cycle이 오는 여부는 view의 모양이나 내용이 변경된 경우에만 해당된다.(뷰의 컨텐츠가 변경된 경우) 뷰의 위치 또는 크기가 변경 되는건 해당되지 않는다.

코드 관점에서 보면 다음 view의 drawing cycle에서 func draw(CGRect)를 통해 뷰를 업데이트 시켜줘야 한다고 알리는 것인데

그럼 func draw(CGRect) 와 view의 drawing cycle이란 뭘까…

기본적으로 draw(CGRect)는 아무것도 실행하지 않는다. 최초로 view가 등장할때를 제외 하고 view의 frame을 변화 시키거나 해도 draw(CGRect) 는 호출되지 않는다.

draw(CGRect)를 trigger 시키는 경우들은 다음과 같다.

  1. view를 부분적으로 가리고 있던 다른 view이동 또는 제거
  2. hidden 프로퍼티를 No로 설정하여, 이전에 숨겨진 view를 다시 볼 수 있게 만들기
  3. view를 화면밖으로 스크롤시킨 후, 화면안으로 다시 이동시키기
  4. view의 setNeedsDisplay 또는 setNeedsDisplayInRect: 메소드를 명시적으로 호출하기

draw(CGRect)는 개발자가 override해서 구현해야 한다. view에 아무런 조치를 취하지 않아도 setNeedsDisplay만 호출해도 draw(CGRect)가 실행된다. 그렇게 하지 않고 draw(CGRect)가 trigger 되게 하려면 contentMode 를 redraw로 해주면 된다.

view.contentMode = .redraw

근데 우린 평소에 이런 조취없이도 잘만 업데이트되는 view를 봐왔다. (!!)

-> 평소에 view property가 변경될때 마다 내부의 setNeedsDisplay()가 호출된다

그럼 언제 쓰냐? (!!)

-> 커스텀 view를 만들고 draw(CGRect) 메소드를 구현하고 특정 사건에 의해 명시적인 업데이트가 필요하면 setNeedsDisplay()를 호출해야 한다.

layoutIfNeeded()

이건 setNeedsLayout 과 비슷하다. 수동으로 예약하는 메소드지만 해당 예약을 즉시 실행시키는 동기적으로 작동하는 메소드이다. view의 update cycle이 와서 layoutSubviews를 호출시키는게 아니라 즉시 layoutSubviews를 호출하는 메소드

-> 따라서 이는 그 즉시 값이 변경되어햐 하는 애니메이션에서 자주 사용된다.

displayIfNeeded()

위에서 설명한 layoutIfNeeded와 비슷한 역할을 수행한다. setNeedsDisplay가 수동으로 예약하는 메소드라면 displayIfNeeded즉시 실행시키는 동기적으로 작동하는 메소드이다.

댓글남기기