Swift + iOS/iOS

[ios] setNeedsLayout vs layoutIfNeeded

군옥수수수 2018. 4. 8. 21:42

[ios] setNeedsLayout vs layoutIfNeeded


안녕하세요. 오늘은 애니매이션 동작에 대해 공부를 하면서 알게 된 내용 중 하나인 setNeedsLayoutlayoutIfNeeded에 대해 포스팅해보려 합니다. 아직은 모든 궁금증이 풀린 것은 아니지만 하나하나 알게 된 것들을 소개해드리도록 하겠습니다.


setNeedsLayoutlayoutIfNeeded를 비교하기 위해서는 먼저 main run loop라는 개념부터 알고있어야 합니다.


Main Run Loop

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


버튼의 터치 이벤트를 @IBAction 메소드가 처리하는 것과 같습니다.

이렇게 발생한 이벤트들을 모두 처리하고 권한이 다시 main run loop로 돌아오게 되고 이 시점을 update cycle이라고 합니다.

Update Cycle

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


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


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


이러한 시간차가 존재한다는 것을 알고있어야 setNeedsLayoutlayoutIfNeeded의 차이를 알 수 있습니다.


이렇게 시간차가 존재하지만 이 시간차는 사용자가 체감할 수 없을 정도로 짧기때문에 사용자는 그러한 시간차를 느끼지 못합니다. 하지만 개발하는 사람은 이러한 시간차를 인지하고 있어야 정확히 원하는 핸들러를 구현할 수 있습니다.


UIView methods

setNeedsLayoutlayoutIfNeeded를 비롯해서 UIView에는 여러 내장 메소드가 존재합니다. 이 두개의 메소드를 들어가기 전에 중요한 메소드 몇 개를 알아보도록 하겠습니다.


- layoutSubViews()


View의 값을 호출한 즉시 변경시켜주는 메소드입니다. 호출되면 해당 View의 모든 Subview들의 layoutSubViews() 또한 연달아 호출합니다. 그렇기 때문에 비용이 많이 드는 메소드이고 그렇기 때문에 직접 호출하는 것은 지양됩니다. 이는 시스템에 의해서 View의 값이 재계산되어야 하는 적절한 시점(update cycle)에 자동으로 호출됩니다.


그렇기 때문에 layoutSubViews를 유도할 수 있는 여러 방법이 존재합니다. 이는 일종의 update cycle에서 layoutSubViews의 호출을 예약하는 행위라고 할 수 있습니다.


UIViewController내의 View가 재계산되어 다시 그려지는 행위가 발생하면, 즉 layoutSubViews가 호출되고 View의 값이 갱신되고나면 뒤이어 UIViewController의 메소드인 viewDidLayoutSubviews가 호출됩니다. 그렇기 때문에 갱신된 View 값에 의존하는 행위들은 viewDidLayoutSubviews에 명시를 해주어야 합니다.


예를들어 Layer값은 자동으로 변경되지 않기 때문에 속한 View의 frame이 변경되면 viewDidLayoutSubviews안에 Layer의 frame을 변경하는 코드를 작성해주어야 합니다.

위에서 언급한 것처럼 layoutSubviews를 update cycle에서 호출되게끔 자동으로 예약을 해주는 상황들이 몇 가지 존재합니다. 즉 다음 상황에서는 시스템이 자동으로 size와 position이 변경되어야 하는 View라고 체크를 하고 update cycle에서는 layoutSubviews가 호출되어 체크된 View의 layer와 position에 변경된 값을 반영합니다.


  • View의 크기를 조절할 때
  • Subview를 추가할 때
  • 사용자가 UIScrollView를 스크롤할 때
  • 디바이스를 회전시켰을 때 (Portrait, Landscape)
  • View의 Auto Layout contraint 값을 변경시켰을 때

위에 나열된 시점에는 자동으로 update cycle에서 layoutSubviews를 호출하는 행위를 예약하는 것입니다. 하지만 이렇게 자동으로 예약하는 행위 이외에도 수동으로 예약할 수 있는 메소드도 존재합니다.


- setNeedsLayout()


layoutSubviews를 예약하는 행위 중 가장 비용이 적게 드는 방법이 setNeedsLayout을 호출하는 것입니다. 이 메소드를 호출한 View는 재계산되어야 하는 View라고 수동으로 체크가 되며 update cycle에서 layoutSubviews가 호출되게 됩니다.


이 메소드는 비동기적으로 작동하기 때문에 호출되고 바로 반환됩니다. 그리고 View의 보여지는 모습은 update cycle에 들어갔을 때 바뀌게 됩니다.


- layoutIfNeeded()


이 메소드는 setNeedsLayout과 같이 수동으로 layoutSubviews를 예약하는 행위이지만 해당 예약을 바로 실행시키는 동기적으로 작동하는 메소드입니다. update cycle이 올 때까지 기다려 layoutSubviews를 호출시키는 것이 아니라 그 즉시 layoutSubviews를 발동시키는 메소드입니다.


만일 main run loop에서 하나의 View가 setNeedsLayout을 호출하고 그 다음 layoutIfNeeded를 호출한다면 layoutIfNeeded는 그 즉시 View의 값이 재계산되고 화면에 반영하기 때문에 setNeedsLayout이 예약한 layoutSubviews 메소드는 update cycle에서 반영해야할 변경된 값이 존재하지 않기 때문에 호출되지 않습니다.


이러한 동작 원리로 layoutIfNeeded는 그 즉시 값이 변경되어야 하는 애니매이션에서 많이 사용됩니다. 만일 setNeedsLayout을 사용한다면 애니매이션 블록에서 그 즉시 View의 값이 변경되는 것이 아니라 추후 update cycle에서 값이 반영되므로 값의 변경은 이루어지지만 애니매이션 효과는 볼 수 없는 것입니다.

setNeedsLayoutlayoutIfNeeded의 차이점은 동기적으로 동작하느냐 비동기적으로 동작하느냐의 차이입니다.


Implementation

먼저 하나의 뷰를 코드로 생성하고 UIViewContorllerview 프로퍼티에 추가합니다.


handleHeightValue부터 살펴보도록 하겠습니다.


  • Constraint의 constant 값을 변경해줍니다. 그리고 애니매이션 블록안에서 layoutIfNeeded를 호출해줌으로써 그 즉시 값의 변경을 반영하기 때문에 애니매이션 효과를 확인하실 수 있습니다.

하지만 handleAlphaValue를 보시면 layoutIfNeeded 없이도 블록안에서 애니매이션 효과가 나타나는 것을 확인하실 수 있습니다. 그 이유는 View의 alpha 속성은 값이 변하면 기본적으로 애니매이션 효과를 지원하는 UIView의 built-in animation 속성 중 하나이기 때문입니다. 이러한 속성들을 UIViewanimatable property라고 합니다.


이에 대해서는 다음 포스팅에서 소개해드리도록 하겠습니다. Auto Layout의 Constraint은 이런 animatable property에 속하지 않기 때문에 layoutIfNeeded라는 메소드를 이용해야 합니다.


마무리

오늘은 이렇게 iOS에서 View를 세심하게 다루려면 반드시 알아야 할 setNeedsLayoutlayoutIfNeeded에 대해 공부를 해보았습니다. 또한 이 두 메소드를 공부하면서 알게 View가 그려지는 과정 및 메소드들의 연관성들도 알게 되었습니다. 


아직은 모든 것이 명료하게 머리에 자리 잡힌 것이 아니지만 더욱 깊이 공부를 하여 머리속에 체계적으로 정리하는 시간을 가져야할 것 같습니다. 혹시 틀린 정보나 더 나은 표현이 있다면 댓글로 피드백 부탁드리겠습니다. 감사합니다.


참고자료


  1. Demystifying iOS Layout - 강추!!
  2. setNeedsLayout vs layoutIfNeeded Explained
  3. iOS swift — setNeedsLayout vs layoutIfNeeded vs layoutSubviews()
  4. SetNeedsLayout vs LayoutIfNeeded