setNeedsDisplay()는 UIView의 인스턴스 메서드로, 뷰를 다시 그려야 하는 것으로 표시합니다. draw(_:)를 직접 호출하는 것과 달리 setNeedsDisplay()는 뷰의 dirty 플래그만 설정하고 즉시 제어를 반환합니다. 시스템은 가장 가까운 렌더링 사이클에서 이 요청을 처리하여 여러 호출을 단일 그리기 작업으로 결합할 수 있습니다. Apple 문서(2025)에 따르면, setNeedsDisplay()를 통한 지연된 다시 그리기는 사용자 정의 UIView의 콘텐츠를 업데이트하는 유일한 올바른 방법입니다.
핵심 사항
setNeedsDisplay()는 UIView 메서드로, 뷰의 콘텐츠가 오래되어 다시 렌더링해야 함을 시스템에 알립니다. 이 메서드는 매개변수를 받지 않으며 void를 반환합니다. 호출 후 뷰는 dirty로 표시되고, 가장 가까운 그리기 사이클에서 시스템이 이 뷰에 대해 draw(_:)를 호출합니다.
핵심 특징은 비동기성입니다. 동일한 뷰에 대해 연속적으로 여러 setNeedsDisplay()를 호출해도 여러 draw(_:) 호출이 발생하지 않습니다 — 시스템이 이를 하나의 작업으로 결합합니다. 이는 성능에 매우 중요합니다: 뷰 속성이 프레임당 여러 번 변경되어도 draw(_:)는 한 번만 호출됩니다.
지연된 다시 그리기 메커니즘은 UIKit 렌더링 사이클을 기반으로 합니다. setNeedsDisplay()가 호출되면 시스템은 내부 플래그 layer.needsDisplay를 true로 설정합니다. 현재 run loop가 끝나면 시스템은 모든 dirty 레이어를 순회하고 각각에 대해 draw(_:)를 호출합니다.
UIKit run loop에는 dirty 뷰가 처리되는 업데이트 단계가 포함됩니다. 이 단계에서는 필요 시 layoutSubviews()가 호출되고, 그 다음 표시된 모든 뷰에 대해 draw(_:)가 호출됩니다. 그리기가 완료되면 결과는 컴포지팅을 위해 Core Animation을 통해 GPU로 전달됩니다.
class ProgressView: UIView {
var progress: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// 배경
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// 진행 바
let fillRect = CGRect(x: rect.minX, y: rect.minY,
width: rect.width * progress, height: rect.height)
ctx.setFillColor(UIColor.systemGreen.cgColor)
ctx.fill(fillRect)
}
}
이 예제에서 progressView는 progress를 업데이트하고 각 setter는 자동으로 setNeedsDisplay()를 호출합니다. 시스템은 run loop가 끝날 때까지 기다렸다가 실제 progress 값으로 draw(_:)를 호출합니다. progress가 프레임당 세 번 변경되어도 draw(_:)는 마지막 값으로 한 번만 실행됩니다.
setNeedsDisplay(_:) 메서드는 CGRect 유형의 rect 매개변수를 받아 다시 그리기 영역을 제한합니다. 이를 통해 뷰 전체가 아닌 변경된 부분만 다시 그릴 수 있습니다. 성능 차이는 변경 사항이 작은 영역에 영향을 미치는 큰 뷰로 작업할 때 두드러집니다.
| 특성 | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| 매개변수 | 없음 | CGRect — 다시 그리기 영역 |
| 다시 그리기 영역 | 전체 뷰 bounds | 지정된 사각형만 |
| 성능 | 큰 뷰에서 낮음 | 부분 변경에서 높음 |
| draw(_:)의 rect 매개변수 | 전체 bounds | 지정된 사각형 |
| 사용 시기 | 전체 콘텐츠 변경 | 부분 변경 (커서, 하이라이트) |
복잡한 그래픽의 경우 먼저 오프스크린 컨텍스트에 전체 장면을 그린 다음 변경된 영역에 대해서만 setNeedsDisplay(_:)를 호출하는 것이 좋습니다. 이는 iOS의 그리기 애플리케이션 및 그래픽 편집기에서 표준 기술입니다.
displayIfNeeded()는 동기 메서드로, run loop가 끝날 때까지 기다리지 않고 계층 구조의 모든 dirty 뷰 다시 그리기를 즉시 시작합니다. 플래그만 설정하는 setNeedsDisplay()와 달리 displayIfNeeded()는 draw(_:)의 즉시 실행을 강제합니다.
Apple은 그리기 결과가 즉시 필요한 극단적인 경우에만 displayIfNeeded()를 사용할 것을 권장합니다 — 예를 들어, 스크린샷을 찍기 전이나 UIGraphicsImageRenderer의 동기 이미지 생성 시. 일반적인 시나리오에서는 메인 스레드를 차단하지 않는 setNeedsDisplay()를 통한 비동기 모델이 선호됩니다.
// 비동기 — 권장
someView.setNeedsDisplay()
// 동기 — 필요할 때만
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) 즉시 호출됨
선행 setNeedsDisplay() 없이 displayIfNeeded()를 호출해도 효과가 없습니다 — 이 메서드는 이미 dirty로 표시된 뷰만 처리합니다. 다시 그리기가 필요한 뷰가 없으면 displayIfNeeded()는 draw(_:)를 호출하지 않고 완료됩니다.
빈번한 setNeedsDisplay() 호출은 특히 애니메이션 중에 성능을 저하시킬 수 있습니다. 주요 최적화 전략을 살펴보겠습니다.
개별 속성이 변경될 때마다 setNeedsDisplay()를 호출하는 대신 변경 사항을 누적하고 모든 업데이트를 적용한 후 메서드를 한 번 호출합니다. 예를 들어, 요소의 색상, 크기 및 위치를 변경할 때 — 모든 속성을 설정한 후 setNeedsDisplay()를 한 번 호출합니다.
속성이 CALayer(backgroundColor, opacity, position, transform)를 통해 애니메이션될 수 있는 경우 draw(_:) 대신 Core Animation을 사용하세요. CALayer 속성은 draw(_:)를 호출하지 않고 GPU에서 업데이트되어 CPU 부하 없이 60 FPS를 제공합니다.
여러 변경 사항을 단일 렌더링 패스로 그룹화하려면 CATransaction을 사용하세요. 이를 통해 트랜잭션 끝에서 여러 setNeedsDisplay() 호출을 하나의 draw(_:) 작업으로 결합할 수 있습니다.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
displayIfNeeded()를 남용하지 마세요 — 동기 다시 그리기는 메인 스레드를 차단하고 프레임 드롭을 유발할 수 있습니다. 현재 run loop가 끝나기 전에 그리기 결과가 중요한 작업에만 사용하세요.
자주 묻는 질문
아니요, draw(_:)를 직접 호출하는 것은 금지됩니다. setNeedsDisplay()는 뷰를 dirty로 올바르게 표시하고 시스템이 렌더링 사이클의 적절한 시점에 draw(_:)를 호출합니다. draw(_:)를 직접 호출하면 캐싱 메커니즘이 무시되고 일관성 없는 상태가 발생할 수 있습니다.
동일한 뷰에 대한 여러 setNeedsDisplay() 호출은 시스템에 의해 단일 draw(_:) 호출로 결합됩니다. 이는 메서드가 dirty 플래그만 설정하고 run loop 끝에서 draw(_:)가 실제로 실행된 후에만 지워지기 때문입니다.
setNeedsDisplay()는 뷰 콘텐츠의 다시 그리기를 요청합니다 (draw(_:) 호출). setNeedsLayout()은 서브뷰의 재배치를 요청합니다 (layoutSubviews() 호출). layoutSubviews()는 크기 변경을 초래할 수 있으며, 이는 다시 그리기를 트리거할 수 있습니다.
setNeedsDisplay(_:)는 부분 변경에 사용해야 합니다: 커서 이동, 선택 영역 색상 변경, 그래프 일부 업데이트. 이는 다시 그리기 영역을 제한하고 전체 뷰를 다시 그리는 것보다 성능을 향상시킵니다.
기본적으로 frame 또는 bounds 변경은 setNeedsDisplay()를 호출하지 않습니다. 시스템은 단순히 뷰를 이동합니다. 크기 변경 시 다시 그리기가 필요한 경우 contentMode 속성을 .redraw로 설정하세요 — 그러면 UIKit이 bounds가 변경될 때마다 자동으로 setNeedsDisplay()를 호출합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.