View, layout를 업데이트 하는 메서드는 대표 4가지가 있다.
항상 헷갈려서.. 한번 정리해보는 시간을 가져보려고 한다!
이에 대해 각각 알아보려고 한다.
먼저 setNeedsDisplay()는 UIView 문서에서 어떻게 사용되는지 볼 수 있다.
공식문서를 보면 아래와 같이 설명되어 있다.
When the actual content of your view changes, it’s your responsibility to notify the system that your view needs to be redrawn. You do this by calling your view’s setNeedsDisplay() or setNeedsDisplay(_:) method of the view. These methods let the system know that it should update the view during the next drawing cycle. Because it waits until the next drawing cycle to update the view, you can call these methods on multiple views to update them at the same time.
이 말은 즉, 뷰의 내용에 변화가 생길때 시스템에게 뷰를 새로 그려야한다. 그럴 때 이 메서드를 불러서 시스템에게 다음 drawing cycle에 뷰를 업데이트 해야한다고 알려주는 것이다.
그럼 이에 대해 알아보기 전에 drawing cycle이 무엇인지부터 알아야될 것 같다.
이는 공식문서에서 또 찾아볼 수 있다. ( drawing and Printing Guide for iOS)
The basic drawing model for subclasses of the UIView class involves updating content on demand. The UIView class makes the update process easier and more efficient; however, by gathering the update requests you make and delivering them to your drawing code at the most appropriate time.
When a view is first shown or when a portion of the view needs to be redrawn, iOS asks the view to draw its content by calling the view’s drawRect: method.
UIView 클래스는 내용을 표시할 때 on-demand 방식으로 그린다.
그래서 처음에 시스템에 내용을 그려달라고 요청한 다음 시스템은 이를 스냅샷으로 캡쳐해둔다.
그리고 이 상태에서 뷰의 내용에 변경이 없다면 View를 그리는 코드를 호출할 수 없다.
하지만 뷰의 내용에 변화가 생긴다면 이때 시스템에 알리고 그 뷰를 그리고 다시 스냅샷으로 캡쳐해두는 프로세스로 진행된다.
그래서 이 말을 보면 뷰의 내용이 변경되면 직접 변경사항을 그리는 것처럼 말하지만, 실제로는 그렇지 않다.
setNeedsDisplay()를 사용해서 View를 무효화시킨다음, 시스템에게 다음 cycle이 올 때 새로 그려달라고 말한다.
그래서 이 시기에 변경사항들은 기다렸다가 현재 실행 루프가 끝난다음, 다음 cycle에 한번에 반영된다!
위의 메서드 말고도 View를 업데이트하는 trigger 역할하는 요소들이 존재한다.
이 방식들이 이루어진다면 뷰는 다음 drawing cycle에 뷰를 업데이트 시킨다.
이제 시스템에서 뷰를 업데이트 하려면 draw(_:) 메서드를 불러야한다.
그럼 이제 이 메서드에 대해 알아볼 필요가 있다.
func draw(_ rect: CGRect)
이 메서드는 파라미터에 있는 rect
라는 사각형 내에서 수신자의 이미지를 그린다고 한다.
rect
에 대한 설명을 더 하자면, 처음 이 뷰를 그릴 때에는 이 직사각형은 뷰의 전체 bounds를 가지고 있다.
하지만 다음에는 수정을 통해 뷰의 일부만을 가질 수도 있다고 말해준다.
처음에는 전체로 가지고 있지만 아마 수정으로 인해 뷰 속에 일부만을 차지한다는 의미를 말하고 있다.
이 메서드는 Core Graphics, UIkit에서 뷰의 내용을 그릴때에만 override
해서 사용한다. 그 외에는 뷰의 내용을 넣을 때 이 메서드를 호출할 필요가 없다.
예를 들어, 단순히 뷰의 배경 색을 넣거나 뷰가 기본 레이어 객체를 사용하여 직접 내용을 설정하는 경우에는 이를 재정의해서 사용할 필요가 없다.
또 하나 주의해야?할 내용으로는 만약 UIView를 하위 클래스로 한다면 이 메서드를 구현할 때 super
를 호출할 필요 없다.
하지만 다른 View 클래스를 하위 클래스로 한다면 구현에서 super
를 호출해야한다.
그래서 이 메서드를 호출하는 시기는 2가지다.
이제 이를 순서대로 뷰에서 보여주는 방식을 정리해보도록 한다.
View load -> 모든 뷰가 준비된 상태(viewDidLoad) -> draw 메서드 호출 -> view update -> (업데이트발생 시) -> setNeedsDisplay 나 setNeedsDisplay(_:) 메서드 호출 -> next drawing cycle에 view update
이제 간단히 뷰가 어떻게 진행되는 지 보았으므로 아까 4가지 메서드를 살펴 볼 수 있다.
먼저 공식문서를 보도록 하자
Marks the receiver’s entire bounds rectangle as needing to be redrawn.
다시 그리는 것이 필요할 때 수신자의 전체 bounds 직사각형을 알려주도록 한다.
그래서 위에서 설명한 것처럼 다음 drawing cycle전 까지 다시 그리지 않고 view를 무효화만 해준다.
이 메서드 사용은 단순히 geometry가 변경되었을 시에 view를 다시 그리는 것이 아니라 뷰의 내용이나 모양이 변경된 경우에만 view를 다시 그린다.
그러나 실제로 코드 작성 할떄 이 메서드를 호출하지 않는 경우가 많았다.
그 이유로는 아래와 같다.
일반적인 View의 프로퍼티가 수정될 떄 트리거로 이 메서드가 알아서 호출되고 다시 그려준다고 한다.
그러므로 만약 custom view를 만들어서 draw 메서드도 구현하고 변경을 명시적으로 업디에트 할 떄에만 이를 적어준다.
그 외에는 아까 4가지 트리거에 속하기 때문에 우리가 설정해줄 필요나 호출할 필요가 없다.
먼저 공식문서를 보면 아래와 같이 설명되어 있다.
Invalidates the current layout of the receiver and triggers a layout update during the next update cycle.
기존에 설명한 내용들과 일치하다. 수신자의 현재 layout를 무효화해주고 다음 drwaing cycle에 layout를 업데이트 하도록 한다.
이는 View와 layout의 차이일뿐 내부는 동일하게 동작한다.
다음 drawing cycle에 한번에 업데이트가 되고, layout이므로 UI 관련이기 때문에 main thread에서 이 메서드를 호출해야한다.
공식문서를 보면 간단하게 설명되어있다.
Lays out subviews.
메서드 명 그대로인 것같다. subviews 들을 레이아웃해준다..
iOS 5.1 이하에서는 이 메서드의 기본 실행은 아무것도 수행하지 않는다.
그렇지 않으면 기본 구현은 하위 view의 크기와 위치를 결정하기 위해 설정한 제약조건을 사용한다고 한다.
하위클래스들이 만약 더 정확한 레이아웃을 수행하기 위해서는 이 메서드를 override
하기도 한다.
만약 오토리사이징이나 제약관련 행동들이 개발자가 원하는 방식으로 하지 않는다면 이 메서드를 동일하게 override
해서 사용할 수 있다.
그리하여 frame 직사각형들을 설정해서 구현해줄 수 있다.
그러나 이 메서드를 직접 호출하지 않고 만약 layout를 강제로 업데이트 하고 싶다면 setNeedsLayout() 메서드를 호출한다.
만약 뷰에서 즉시 레이아웃 업데이트가 필요하다면 layoutIfNeeded() 메서드를 호출하면 된다.
공식문서를 보면 위에 layoutSubview() 에서 언급 된 것처럼 즉시 subviews의 레이아웃을 업데이트할 때 이 메서드를 호출한다.
Lays out the subviews immediately, if layout updates are pending.
이 메서드는 강제로 뷰의 레이아웃을 즉시 업데이트할 때 사용 한다. Auto layout를 사용할 때, 레이아웃 엔진은 constraints에서 변화를 만족하는 것이 필요할 때 뷰의 위치를 바꾼다고 한다.
이 메서드는 메시지를 루트 view로 수신하는 view를 사용하여 루트에서 시작하는 view 하위 루트를 배치한다.
만약 보류 중인 레이아웃 업데이트하는 것이 없다면 이 메서드는 레이아웃을 변경하거나 레이아웃 관련 콜백을 호출하지 않고 종료한다.
layoutIfNeeded는 동기호출이고 setNeedsLayout는 비동기 호출이다!
동기 호출이므로 업데이트 주기 없이 즉시 반영하고 setNeedsLayout는 비동기 호출이므로 메서드가 완료되어 즉시 반환되도록 한다.
그래서 업데이트 주기때 비동기로 이 작업이 호출되는 것을 볼 수 있다.
공식문서를 보면 업데이트가 필요하는 곳에서 레이어에 대한 업데이트 과정을 초기화한다고 한다.
Initiates the update process for a layer if it is currently marked as needing an update.
이 메서드는 일반적인 update cycle의 밖에서 레이어의 내용들에게 강제로 업데이트할 때 필요하다.
그래서 이 방법은 잘 사용하지 않는다고 공식문서에 쓰여있다! 아무래도 update cycle과 다른 강제 업데이트이기 떄문인듯싶다!
그래서 공식문서에서는 setNeedsLayout을 사용하는 것을 권장하고 있다.
참고사이트
https://developer.apple.com/documentation/uikit/uiview/1622482-layoutsubviews
https://developer.apple.com/documentation/uikit/uiview
https://developer.apple.com/library/archive/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW1