setNeedsLayout과 관련된 내용을 학습하다가 Runloop, UpdateCycle을 알게 되었고 Display, Layout, Constraint 업데이트와 관련된 내용이 있다는 점을 알게 되어 이에 기반한 내용을 추가적으로 작성합니다!
혹시 이 전 게시글의 내용이 궁금하시다면 지난 게시글을 한번 보셨으면 좋겠습니다!
간략하게 요약하면 Update Cycle이 발생할 때 크게 layout, display, constraint가 업데이트가 됩니다.
이렇게 UIView와 관련된 것들이 어떻게 코드에서 어떻게 작동하는지 더 알아보겠습니다!
공식 문서와 부족한 내용은 https://tech.gc.com/demystifying-ios-layout/ 를 참고하여 게시글을 작성하였습니다.
UIView에는 몇가지 책임이 있다고 합니다.
Drawing and animation
Layout and subview management
Event Handling
Draw & animation은 Display, Layout and Subview Management는 Layout, Constraint를 다루는 느낌이 듭니다!
View Drawing은 필요하면 수행된다. 당연한 소리같습니다. 처음 뷰가 보여질때나 layout의 변화로 인해 일부분이라도 보여지면 시스템이 컨텐츠를 그리라는 명령을 합니다. UIKit이나 Core Graphics같은 custom contents를 담당하는 내용을 포함하는 View는 draw(_:)
메소드를 호출하여 컨텐츠를 그립니다. 이 메소드를 호출하기 전에 시스템에서 자동적으로 설정(set)이 되어 있고 view의 컨텐츠를 그리는 책임이 있다!
이 메서드는 뷰가 처음 보여질때나 이벤트가 발생해 보여지는 뷰가 invalidate될 때 호출되는데, 직접 호출하면 절대 안된다고 합니다!대신에 setNeedsDisplay(_:), setNeedsDisplay()를 호출해야 된다!
두 메서드의 차이점은 전체를 다시 그릴지, 일부의 Rectangle을 다시 그릴지를 설정하는 차이입니다.
시스템에 뷰의 컨텐츠를 redraw를 해달라는 메서드입니다!
이 메서드를 실행하면 요청 후 즉시 return됩니다. Update Cycle을 보셨다면 아시겠지만 다음 Update Cycle에 실행됩니다!
이 메서드는 View의 Display(BackgroundColor, Tintcolor, UIImage)와 같은 redraw에만 사용해야 된다. 간단한 위치나 크기의 변화는 redraw되지 않는다!
전자의 setNeedsDisplay(_ rect: CGRect)
의 경우 뷰를 그리는 동작 방식은 같으나 전체 View가 아닌 특정 지역만 redraw합니다.
뷰의 UI 컴포넌트를 업데이트 하는 것은 View의 dirty flag를 활성화 시켜서 명시적으로 setNeedsDisplay를 호출하지 않아도 다음 Update Cycle에 뷰를 다시 그려지도록 유도합니다. 하지만 UI컴포넌트와 직접 관련이 없지만 Update Cycle에 뷰를 업데이트하고 싶다면 명시적으로 setNeedsDisplay를 호출해야 합니다.
이전 게시글에서도 언급했지만 Update Cycle의 간격은 1/60초로 사용자가 느끼기엔 매우 빠른시간입니다!
display를 redraw한다는 점에서 유사한 점이 있지만 다시 그리는 시점에서 차이가 발생합니다!
setNeedsDisplay()
를 호출하게 되면 즉시 업데이트되는 것이 아닌 다음 Update Cycle에 display가 업데이트되는 것과 다르게 호출 즉시 update process를 시작합니다.
필요시에 업데이트를 즉시 하는것입니다. 공식문서에는 setNeedsDisplay()를 호출하여 다음 사이클동안 업데이트하는 것을 권장하네요!
이부분은 공식문서의 내용보다 https://tech.gc.com/demystifying-ios-layout/ 이 더 잘나와 있어서 번역을 했습니다!
UIView와 자식들의 위치와 크기(layout)을 재조정합니다. 현재 뷰 뿐만 아니라 subViews들의 위치와 사이즈도 조정한다. 이 메서드는 뷰의 모든 서브뷰들의 layoutSubviews()
들을 재귀적으로 호출하기 때문에 부하가 매우 크다. view의 frame을 다시 계산할 때 이 메서드가 호출되기 때문에 layoutSubviews
를 오버라이드해서 frame의 크기와 사이즈를 조절할 수 있다. 그러나 layoutSubviews
를 직접 호출해서는 안된다. layoutSubviews
를 시스템이 호출하도록 유도하는 여러 개의 방식들이 존재한다. 이러한 것들은 모두 runloop이 돌아가는 동안 layoutSubviews
가 실행되는 시점이 다르며 layoutSubviews
를 직접 호출하는 것 보다 부하가 덜 하다.
layoutSubviews
가 완료될 때, viewDidLayoutSubviews가 View를 소유한 ViewController에서 호출된다. layoutSubviews
는 View의 layout이 변화했다는 유일한 콜백이므로 레이아웃과 관련된 로직을 viewDidLayoutSubviews
에 호출해야 합니다. 이것이 오래된 레이아웃이나 위치 변수를 다른 계산에 사용하는 실수를 막는 유일한 방법이다.
앞서 말씀드린 layoutSubviews
대신에 호출해야 하는 메서드를 호출할 필요 없이 자동으로 refresh trigger가 발생하는 경우가 있다.
위의 방법들은 시스템이 View의 위치가 변했고, 다시 계산되도록 layoutSubviews
를 호출되게 된다..
이외에 직접 호출하는 방법들이 2가지가 존재.
앞서 setNeedsDisplay()
와 동작 방식이 유사합니다!
setNeedsDisplay()
호출되면 즉시 리턴되고 View를 즉시 업데이트 하는 것이 아니라 다음 Update Cycle에 Layout을 업데이트 합니다.
이 메서드는 displayIfNeeded
와 유사합니다. 다음 Update Cycle에 View의 layout을 재정의하는 것 아니라 메서드 호출 즉시 Layout을 재정의합니다.
하지만 무조건 호출되는 것은 아니다! view가 refresh되어야 함을 감지해야 한다.
layoutIfNeeded는 Constraints를 애니메이션하는 상황에 유용합니다! 애니메이션을 시작하기 전 layoutIfNeeded를 호출하여 모든 레이아웃 업데이트가 애니메이션 전에 수행되도록 전파할 수 있습니다.
이 메서드는 Auto layout을 이용하는 View의 Constraints를 동적으로 변경할 때 사용될 수 있다.
Layout의 layoutSubviews()
와 Display의 draw()
와 같이 updateConstraints
는 오직 오버라이딩되어야 하며 명시적으로 호출하면 안된다.
정적인 Constraints에 대하여 Interface Builder나 View의 생성자나 ViewDidLoad
에서 정의되어야 한다.
일반적으로 Constraints를 활성화/비활성화하거나 Constraint의 우선 순위나 Constant를 변경하거나 View의 계층에서 삭제하는 것은 updateContraints
를 다음 Update Cycle에서 호출하게 합니다. 이 또한 명시적으로 호출하는 방법이 존재합니다!
setNeedsUpdateConstraints
를 호출하는 것은 다음 Update Cycle에서 Constraint가 업데이트 되도록 한다.
앞서 보신 setNeedsDisplay()
와 setNeedsLayout()
와 비슷하게 동작합니다.
익숙한 IfNeeds 키워드가 보이죠? 이 역시 유사합니다!
하지만 Auto Layout을 사용하는 뷰에만 유효합니다.