먹고 기도하고 코딩하라

레이아웃 업데이트를 위한 메소드 setNeedsLayout(), ifLayoutNeeded() 본문

앱/Swift

레이아웃 업데이트를 위한 메소드 setNeedsLayout(), ifLayoutNeeded()

사과먹는사람 2024. 3. 17. 19:03
728x90
728x90

 

무지 기본적인 것에 무지한 관계로..

뷰를 업데이트하는 레이아웃 메소드를 살펴본다.

뷰 레이아웃이 어떻게 이뤄지는지 알기 위해서는 일단 런루프를 조금 알아보는 게 좋겠다.

 

런루프

swift에는 런루프(run loop)라는 게 있다.

런루프는 Event-driven 프로그래밍에서 사용된다.

조금 추상적인 개념이지만 어떤 이벤트를 대기하면서 스레드를 좀 놀려놨다가 이벤트가 생기면 스레드에게 그것을 처리하게끔 시키는 스레드 관리 시스템이라고 보면 이해가 쉬울 것 같다. (polling이 아니라 notification을 기다리는 느낌이랄까) 

그리고 메인 스레드에서 실행되는 런루프를 메인 런루프라고 부른다.

메인 런루프가 하는 주된 일은 앱 라이프사이클 주기 동안 계속 사용자 이벤트를 대기하고, 이벤트가 발생하면 그에 따라 이벤트 핸들러를 실행한다. 그런데, 이벤트가 발생할 때마다 루프가 돌진 않고 이벤트 큐에 이벤트들을 모아놨다가 사이클이 끝날 때 한꺼번에 처리하는 식이다.

 

메인 스레드의 중요한 특징 중 하나가 UI 작업을 하는 스레드라는 것이다.

그렇다. 메인 런루프도 UIView의 드로잉 사이클을 관리하는 역할을 한다.

UIView에 변경 코드를 준다고 즉각적으로 갱신되지는 않는다.

사이클이 빠르기 때문에 거의 즉시 갱신되는 것으로 보이지만, 엄밀히 따지면 그렇지 않다.

let myButton = UIButton()
myButton.frame = .CGRect(x: 30, y: 0)

이런 코드를 짰다고 가정하면 frame이 실제로 변경되어 레이아웃에 반영되는 시점은 드로잉 사이클이 끝난 시점이다.

 

런루프에 대해 쓰는 글이 아니라서 간단히 요약만 하자면,

  • 메인 스레드의 메인 런루프가 UIView 드로잉 사이클을 관리한다. 즉, UIView에 일어나는 이벤트들을 이벤트 큐에 모았다가 사이클이 종료될 때 한번에 처리하고 앱 객체에 전달해서 뷰를 업데이트한다.
  • 더 좋은 요약글은 여기

 

 

레이아웃 업데이트 메소드

setNeedsLayout()

receiver(뷰)의 현재 레이아웃을 더이상 유효하지 않도록 만들고, 다음 업데이트 사이클에 레이아웃 업데이트를 하도록 한다.

런루프의 다음 업데이트 사이클에 갱신하도록, 즉 다음 사이클에 layoutSubviews()를 호출하도록 요청하는 비동기 메소드다. 비동기이기 때문에 호출 뒤 바로 반환된다.

문서에는 서브뷰 레이아웃을 다시 하고 싶은 뷰를 대상으로 메인 스레드에서 호출하도록 가이드하고 있다. 즉시 업데이트하는 것을 강제하는 메소드가 아니기 때문에 뷰들이 업데이트되기 전에 뷰 레이아웃을 유효하지 않도록 만든다. 즉, 뷰에 속한 서브뷰들이 다른 업데이트 사이클에 따로따로 업데이트되는 것이 아니라 한 사이클에 몰아서 처리되는 것을 의미한다. 그래서 퍼포먼스 올리기에는 좋다. 

 

layoutIfNeeded()

런루프에 즉시 갱신을 요청한다. 즉시 layoutSubviews()를 호출하는 동기 메소드다.

레이아웃을 강제로 즉시 업데이트하고 싶을 때 사용한다. AutoLayout을 사용할 때는, 레이아웃 엔진이 constraint를 만족하는 선에서 뷰 위치를 업데이트하도록 한다.

 

layoutSubviews()

대상이 되는 뷰와 그 하위 subview들의 위치, 사이즈를 다시 계산한다.

호출 즉시 값을 변경시키게 되는데, 웬만해서는 직접 호출하지 않는 것이 좋다. cost가 좀 부담이 되는 작업이다 보니...

setNeedsLayout(), layoutIfNeeded() 모두 layoutSubviews()를 간접적으로 호출하는 메소드인데 그 시점의 차이가 있는 것이다.

 

 

동기, 비동기인 것과 업데이트 요청 시점을 제외하면 실질적으로 무슨 차이인지 솔직히 와닿지는 않는다.

이 블로그의 애니메이션 예제를 보면 이해되리라 생각한다.

뷰의 width, height값을 바꾸고 animate시키는데, layoutIfNeeded()를 할 때는 2초 동안 점차로 커지는 애니메이션 효과를 볼 수 있지만 setNeedsLayout()을 할 때는 애니메이션 효과 없이 바로 다음 업데이트 사이클에 (300, 300) 크기로 변경되는 것이다. 

그래서 애니메이션을 줄 때는 layoutIfNeeded()를 써줘야 원하는 결과를 얻을 수 있다.

 

 

 

References

 

 

728x90
반응형
Comments