draw(_:)와 drawRect — 개념, 호출 및 오버라이딩

저자: IT Sectr 게시일: 2026-07-20 읽는 시간: 7 분

draw(_:) / drawRect는 UIView 클래스(Swift)의 메서드와 Objective-C의 대응 메서드(drawRect:)로, Core Graphics를 사용하여 뷰의 콘텐츠를 렌더링합니다. 시스템은 뷰가 화면에 처음 나타날 때와 setNeedsDisplay() 호출 후에 자동으로 이 메서드를 호출합니다. Apple Documentation(2025)에 따르면, draw(_:)는 개발자가 사용자 정의 그리기를 위해 현재 화면의 그래픽 컨텍스트에 접근할 수 있는 유일한 메서드입니다. draw(_:)를 오버라이딩하면 단순한 기하학적 도형부터 복잡한 애니메이션 그래픽까지 구성 요소의 모양을 완벽하게 제어할 수 있습니다.

주요 포인트

  • draw(_:) — Core Graphics를 통한 사용자 정의 그리기용 UIView 메서드, 개발자가 직접 호출하지 않음
  • drawRect: — Objective-C에서 draw(_:)에 해당, 구문적으로 다르지만 기능적으로 동일
  • CGContext — 모든 그리기 작업을 위해 draw(_:) 내에서 사용 가능한 그래픽 컨텍스트
  • setNeedsDisplay() — 개발자 측에서 draw(_:)의 다시 그리기를 요청하는 유일한 올바른 방법
  • UIGraphicsGetCurrentContext() — draw(_:) 내에서 현재 컨텍스트를 가져오는 함수, 그리기 전 필수 단계

draw(_:) / drawRect란

draw(_:)는 UIView의 인스턴스 메서드로, UIKit이 뷰의 콘텐츠를 렌더링하기 위해 호출합니다. 이 메서드 내에서 개발자는 CGContext 그래픽 컨텍스트에 접근하여 Core Graphics API를 사용해 선, 채우기, 텍스트 및 이미지를 그립니다. Objective-C의 drawRect:는 동일한 기능을 수행하지만 구문이 다릅니다. 유일한 매개변수는 다시 그릴 영역을 지정하는 CGRect입니다.

Apple Engineering(2024)에 따르면, draw(_:)는 CPU 기반 비트맵 그래픽 컨텍스트 렌더링을 통해 작동하여 최대의 유연성을 제공하지만 CALayer에 비해 더 많은 리소스가 필요합니다. draw(_:) 사용 결정은 그래픽의 복잡성과 성능 요구 사항에 따라 이루어집니다.

Swift와 Objective-C의 메서드 시그니처

Swift에서 메서드는 override func draw(_ rect: CGRect)로 선언되며, rect는 다시 그려야 하는 사각형입니다. Objective-C에서 시그니처는 - (void)drawRect:(CGRect)rect입니다. setNeedsDisplay(_:)를 통한 부분 다시 그리기 중에는 rect 매개변수가 뷰의 bounds보다 작을 수 있습니다.

objective-c
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);
}

시스템이 draw(_:)를 호출하는 시기

시스템은 엄격하게 정의된 시나리오에서 자동으로 draw(_:)를 호출합니다. 이러한 트리거를 이해하면 불필요한 다시 그리기를 방지하고 뷰 성능을 최적화하는 데 도움이 됩니다. 아래는 메서드의 자동 호출 사례입니다.

  • 첫 번째 렌더링 — 뷰가 계층 구조에 처음 추가되어 화면에 표시될 때
  • setNeedsDisplay() — 이 메서드 호출 후, 시스템이 다음 그리기 사이클에서 다시 그림
  • setNeedsDisplay(_:) — 위와 동일하지만 다시 그릴 특정 사각형을 지정
  • contentMode — bounds가 변경되고 contentMode가 다시 그리기를 요구하는 경우(예: .redraw)
  • setNeedsLayout() — 경우에 따라 하위 뷰 재배치 후 다시 그리기가 필요할 수 있음

Apple 문서는 경고합니다: draw(_:)를 직접 호출하지 마십시오. 시스템이 자체적으로 그리기 시기를 결정하며, 직접 호출은 내부 캐싱 메커니즘을 손상시킵니다. 다시 그리기를 요청하려면 항상 setNeedsDisplay() 또는 setNeedsDisplay(_:)를 사용하세요.

Swift에서 draw(_:)를 오버라이딩하는 방법

Swift에서 draw(_:)를 오버라이딩하려면 그래픽 컨텍스트를 가져온 후 Core Graphics를 호출합니다. 그리기의 논리적 블록에 대해 별도의 메서드를 만드는 것이 좋습니다. 이렇게 하면 코드 가독성과 테스트 용이성이 향상됩니다.

swift
override func draw(_ rect: CGRect) {
    super.draw(rect)

    guard let context = UIGraphicsGetCurrentContext() else { return }

    // Line parameters
    context.setStrokeColor(UIColor.darkGray.cgColor)
    context.setLineWidth(2.0)

    // Drawing a triangle
    context.move(to: CGPoint(x: rect.midX, y: rect.minY + 10))
    context.addLine(to: CGPoint(x: rect.maxX - 10, y: rect.maxY - 10))
    context.addLine(to: CGPoint(x: rect.minX + 10, y: rect.maxY - 10))
    context.closePath()
    context.strokePath()
}

이 예제에서 draw(_:)는 2픽셀 두께의 진회색 윤곽선을 가진 삼각형을 그립니다. 메서드 시작 부분에서 super.draw(rect)를 호출하는 것은 Apple이 권장합니다. 부모 그리기 로직을 유지하기 위해서이며, UIView의 기본 draw(_:) 구현은 비어 있습니다.

draw(_:)의 멱등성 규칙

draw(_:)의 각 호출은 동일한 입력 데이터에 대해 동일한 결과를 생성해야 합니다. 이를 통해 시스템은 결과를 캐시하고 뷰의 콘텐츠가 변경되지 않은 경우 draw(_:)를 다시 호출하지 않습니다. draw(_:) 내에서 난수 값, 시스템 시간 또는 네트워크 요청을 사용하지 마세요.

Objective-C의 drawRect:와 draw(_:)의 차이점

drawRect:는 역사적으로 iOS 2.0에서 Objective-C와 함께 등장한 첫 번째 버전의 메서드입니다. Swift에서는 외부 매개변수 _를 사용하여 메서드 이름이 draw(_:)로 변경되었습니다. 기능적으로 이 메서드들은 동일합니다. 둘 다 다시 그릴 영역의 CGRect를 받고 UIGraphicsGetCurrentContext()를 사용하여 그래픽 컨텍스트에 접근합니다.

특성draw(_:) (Swift)drawRect: (Objective-C)
시그니처override func draw(_ rect: CGRect)- (void)drawRect:(CGRect)rect
super 호출super.draw(rect)[super drawRect:rect]
컨텍스트UIGraphicsGetCurrentContext()UIGraphicsGetCurrentContext()
rect 매개변수rect: CGRectCGRect rect
성능동일동일

Objective-C에서 Swift로 프로젝트를 마이그레이션할 때 메서드 이름 변경은 첫 번째 작업 중 하나입니다. Xcode는 자동 변환기를 제공하지만 drawRect:는 수동으로 draw(_:)로 업데이트해야 합니다. Apple(2024)에 따르면 Swift 버전 draw(_:)가 새 프로젝트에 선호됩니다.

draw(_:)에서 그리기 최적화

draw(_:)는 CPU에서 실행되며, 최적이 아닌 구현은 프레임 드롭과 낮은 성능을 초래할 수 있습니다. Apple은 그리기를 가속화하기 위한 몇 가지 검증된 접근 방식을 권장합니다.

그리기 작업 수 최소화

각 Core Graphics 작업(move(to:), addLine(to:), strokePath)에는 오버헤드가 있습니다. 작업을 그룹화하고 복잡한 모양에는 CGPath를 사용하세요. 경로는 한 번 생성되고 각 draw(_:) 호출 시 재사용됩니다.

벡터 객체에는 UIBezierPath 사용

UIBezierPath는 CGPath의 Objective-C 래퍼로, 모양을 만들기 위한 간단한 API를 제공합니다. UIBezierPath를 미리 생성하고(예: 초기화자에서) draw(_:) 내에서 fill() 또는 stroke()만 호출하세요.

swift
private let starPath: UIBezierPath = {
    let path = UIBezierPath()
    // Building a star shape
    path.move(to: CGPoint(x: 50, y: 0))
    for i in 1...5 {
        let angle = CGFloat(i) * 4 * CGFloat.pi / 5
        path.addLine(to: CGPoint(x: 50 + 40 * cos(angle),
                               y: 50 + 40 * sin(angle)))
    }
    path.close()
    return path
}()

override func draw(_ rect: CGRect) {
    UIColor.systemYellow.setFill()
    starPath.fill()
}

draw(_:) 작업 시 일반적인 실수

개발자들은 draw(_:)를 오버라이딩할 때 종종 같은 실수를 합니다. 이러한 패턴을 인식하면 버그와 성능 저하를 방지하는 데 도움이 됩니다. 가장 일반적인 문제와 해결 방법을 살펴보겠습니다.

  • draw(_:) 직접 호출 — draw(_:)를 직접 호출하지 마십시오. 다시 그리기를 요청하려면 setNeedsDisplay()를 사용하세요. 직접 호출은 캐싱을 손상시키고 잘못된 표시를 초래할 수 있습니다.
  • draw(_:) 내에서 무거운 계산 — draw(_:)는 가능한 한 가벼워야 합니다. UIBezierPath, 이미지 및 기타 무거운 객체는 메서드 외부에서 생성하고 한 번만 초기화하세요.
  • draw(_:) 내에서 객체 생성 — draw(_:) 내에서 UIColor, UIFont 및 UIGraphicsImageRenderer 구성을 하면 불필요한 메모리 부하가 발생합니다. 객체 생성을 클래스 속성으로 이동하세요.
  • rect 매개변수 무시 — rect는 다시 그려야 하는 영역을 지정합니다. rect 외부의 그리기는 시스템에 의해 폐기되지만 리소스를 소비합니다. 그리기 전에 rect와의 교차를 확인하세요.
  • super.draw(rect) 누락 — UIView의 구현이 비어 있지만, Apple은 향후 UIKit 변경과의 호환성을 위해 super.draw(rect)를 호출할 것을 권장합니다.

이러한 규칙을 따르면 모든 iOS 프로젝트에서 안정적이고 빠른 사용자 정의 뷰 그리기를 보장할 수 있습니다. 실제 실행 시간과 병목 지점을 확인하려면 Instruments(Core Animation)를 통해 draw(_:)를 프로파일링하세요.

자주 묻는 질문

draw(_:)를 직접 호출할 수 있나요?

아니요, draw(_:)의 직접 호출은 Apple 문서에서 금지되어 있습니다. 시스템이 자체적으로 그리기 사이클을 관리합니다. 다시 그리기를 요청하려면 setNeedsDisplay()를 사용하세요. 가장 가까운 렌더링 사이클에서 뷰를 업데이트해야 하는 것으로 올바르게 표시합니다.

drawRect:와 draw(_:)는 어떻게 다른가요?

기능적으로 이 메서드들은 동일합니다. drawRect:는 Objective-C에서 사용되고, draw(_:)는 Swift에서 사용됩니다. 둘 다 다시 그릴 영역의 CGRect를 받고 UIGraphicsGetCurrentContext()를 통해 동일한 Core Graphics 컨텍스트를 사용합니다.

빈 UIView에서 draw(_:)가 호출되지 않는 이유는?

Apple은 렌더링을 최적화합니다. UIView에 오버라이딩된 draw(_:)가 없으면 시스템은 이를 위한 비트맵 컨텍스트를 생성하지 않습니다. 이렇게 하여 메모리를 절약합니다. 오버라이딩이 존재하지만 메서드가 호출되지 않으면 뷰의 프레임이 0이 아니고 뷰가 표시되는지 확인하세요.

시스템이 draw(_:)를 얼마나 자주 호출하나요?

필요할 때만: 첫 번째 렌더링, setNeedsDisplay() 후, contentMode = .redraw로 bounds가 변경될 때. 정적 상태에서는 draw(_:)가 다시 호출되지 않아 CPU와 배터리 리소스를 절약합니다.

Swift에서 super.draw(rect)를 호출해야 하나요?

Apple은 오버라이딩된 메서드의 시작 부분에서 super.draw(rect)를 호출할 것을 권장합니다. 현재 UIView 구현은 비어 있지만, super 호출은 향후 UIKit 버전과의 호환성을 보장하며 좋은 관행입니다.

요약

  • draw(_:) / drawRect — Swift 및 Objective-C에서 UIView의 주요 그리기 메서드, 시스템에 의해 자동으로 호출됨
  • CGContext — UIGraphicsGetCurrentContext()를 통해 draw(_:) 내에서 사용 가능한 Core Graphics 컨텍스트
  • setNeedsDisplay() — 다시 그리기를 요청하는 올바른 방법; draw(_:) 직접 호출은 금지됨
  • 멱등성 — 올바른 캐싱을 위해 draw(_:)는 동일한 입력 데이터로 동일한 결과를 생성해야 함
  • UIBezierPath — 각 호출 시 CPU 부하를 최소화하기 위해 draw(_:) 외부에서 경로 생성
  • rect 매개변수 — 다시 그리기 영역을 포함; 최적화를 위해 교차 사용 — 외부에서 그리지 않음
  • 프로파일링 — 느린 작업을 식별하기 위해 Instruments Core Animation을 통해 draw(_:) 성능 확인

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기