[ios] setNeedsLayout vs layoutIfNeeded
안녕하세요. 오늘은 애니매이션 동작에 대해 공부를 하면서 알게 된 내용 중 하나인 setNeedsLayout
과 layoutIfNeeded
에 대해 포스팅해보려 합니다. 아직은 모든 궁금증이 풀린 것은 아니지만 하나하나 알게 된 것들을 소개해드리도록 하겠습니다.
setNeedsLayout
과 layoutIfNeeded
를 비교하기 위해서는 먼저 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 값을 변경하는 코드와 실제로 변경된 값이 반영되는 시점에는 시간차가 존재한다는 뜻입니다.
이러한 시간차가 존재한다는 것을 알고있어야 setNeedsLayout
과 layoutIfNeeded
의 차이를 알 수 있습니다.
이렇게 시간차가 존재하지만 이 시간차는 사용자가 체감할 수 없을 정도로 짧기때문에 사용자는 그러한 시간차를 느끼지 못합니다. 하지만 개발하는 사람은 이러한 시간차를 인지하고 있어야 정확히 원하는 핸들러를 구현할 수 있습니다.
UIView methods
setNeedsLayout
과 layoutIfNeeded
를 비롯해서 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에서 값이 반영되므로 값의 변경은 이루어지지만 애니매이션 효과는 볼 수 없는 것입니다.
setNeedsLayout
과 layoutIfNeeded
의 차이점은 동기적으로 동작하느냐 비동기적으로 동작하느냐의 차이입니다.
Implementation
먼저 하나의 뷰를 코드로 생성하고 UIViewContorller
의 view
프로퍼티에 추가합니다.