Custom UIView는 UIKit 컴포넌트 UIView의 서브클래스로, 개발자가 생명주기 및 드로잉 메서드를 재정의하여 고유한 시각적 요소를 만듭니다. 표준 UIView(UIButton, UILabel, UIImageView)는 대부분의 일반적인 시나리오를 처리하지만, 비표준 그래픽, 애니메이션 또는 상호작용이 필요할 때는 사용자 정의 UIView를 만드는 것이 필수적입니다. Apple Documentation(2025)에 따르면, 사용자 정의 UIView는 비표준 인터페이스 솔루션이 있는 App Store 애플리케이션의 68%에서 사용됩니다. 이 접근 방식은 드로잉, 터치 처리 및 뷰 내 요소 레이아웃에 대한 완전한 제어를 제공합니다.
핵심 요점
Custom UIView는 UIView에서 상속받는 사용자 정의 클래스로, 개발자가 표준 메서드를 재정의하여 사용자 정의 표시 및 상호작용 로직을 구현합니다. UIKit에는 많은 내장 컴포넌트가 포함되어 있지만, 애니메이션 차트, 사용자 정의 스위치, 자유 그리기 캔버스, 게임 요소 또는 데이터 시각화와 같은 모든 시나리오를 다루지는 않으므로 사용자 정의 구현이 필요합니다.
Apple은 표준 컴포넌트가 필요한 기능을 제공할 수 없거나 동일한 사용자 정의 요소가 애플리케이션의 여러 곳에서 사용될 때 Custom UIView 생성을 권장합니다. WWDC 2024에 따르면, 중간 규모 프로젝트에서 사용자 정의 뷰는 전체 UIView의 평균 15-20%를 차지합니다.
Custom UIView는 차트 및 다이어그램 구축(Core Graphics로 선과 도형 그리기), 사용자 정의 진행 표시기, 애니메이션 배경, 손가락 그리기 요소 및 실시간 데이터 시각화에 사용됩니다. 각각의 경우에서 개발자는 CGContext에 대한 완전한 액세스 권한을 얻고 모든 지오메트리를 그릴 수 있습니다.
요소를 표준 UIKit 컴포넌트(UIButton, UIImageView, UILabel)와 Auto Layout 및 속성 설정을 사용하여 조립할 수 있다면 UIView 서브클래스를 만드는 것은 과도합니다. Apple은 먼저 기성 뷰의 구성을 시도하고 기능이 충분하지 않을 때만 사용자 정의 드로잉으로 전환할 것을 권장합니다.
사용자 정의 UIView 생성은 UIView에서 상속받는 클래스 선언과 필수 초기화 메서드 구현으로 시작됩니다. 최소 구현에는 코드에서 생성을 위한 init(frame:)과 Storyboard 또는 XIB에서 로드를 위한 init(coder:)가 포함됩니다.
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .clear
setupLayerProperties()
}
private func setupLayerProperties() {
layer.cornerRadius = bounds.width / 2
layer.masksToBounds = true
}
}
setupView() 메서드에서 투명 배경, 레이어 설정 등의 초기 속성이 설정됩니다. 뷰가 Interface Builder에 표시되는 경우 라이브 미리보기를 위해 @IBDesignable 및 @IBInspectable을 추가하는 것이 좋습니다.
Custom UIView는 특정 순서로 호출되는 일련의 생명주기 메서드를 통해 시스템에 의해 관리됩니다. 이 주기를 이해하는 것은 뷰의 올바른 설정 및 드로잉에 매우 중요합니다.
| 메서드 | 호출 시점 | 목적 |
|---|---|---|
| init(frame:) | 코드에서 뷰 생성 | 속성 초기화, 하위 뷰 추가 |
| init(coder:) | Storyboard/XIB에서 로드 | 역직렬화 및 초기 설정 |
| layoutSubviews() | 프레임 변경 시 | 하위 요소 지오메트리 재계산 |
| draw(_:) | 첫 표시 시 또는 setNeedsDisplay() 후 | Core Graphics를 통한 콘텐츠 드로잉 |
| didMoveToSuperview() | 계층 구조에 추가된 후 | 최종 설정, 애니메이션 시작 |
모든 메서드는 시스템에 의해 자동으로 호출되며, 개발자가 직접 호출할 필요가 없습니다. 예외는 setNeedsDisplay()로, 시스템에 draw(_:)를 다시 호출해야 함을 알립니다.
draw(_:)는 Custom UIView에서 사용자 정의 드로잉을 위한 핵심 메서드입니다. 이 메서드 내에서 개발자는 CGContext(그래픽 컨텍스트)에 액세스하여 Core Graphics를 사용해 선, 도형, 텍스트 및 이미지를 그릴 수 있습니다.
시스템은 뷰가 화면에 처음 나타날 때 자동으로 draw(_:)를 호출합니다. 이후 호출은 뷰를 다시 그려야 함을 표시하는 setNeedsDisplay()에 의해 트리거됩니다. 중요: draw(_:)를 직접 호출하지 마세요. 캐싱 메커니즘이 손상되고 성능이 저하됩니다.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// 배경 채우기
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// 원 그리기
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
이 예제에서 draw(_:)는 배경을 파란색으로 채우고 가장자리에서 20픽셀 간격으로 흰색 원을 그립니다. draw(_:)의 각 호출은 멱등적이어야 합니다. 동일한 매개변수로 여러 번 호출해도 동일한 결과를 생성해야 합니다.
Apple은 draw(_:) 내에서 작업을 최소화할 것을 권장합니다. UIBezierPath를 미리 생성하고, 이미지를 캐싱하며, 무거운 계산을 수행하지 마세요. 뷰가 정적인 경우 지속적인 다시 그리기 대신 렌더링된 이미지와 함께 UIImageView 사용을 고려하세요.
CALayer는 UIView의 시각적 콘텐츠를 관리하는 기본 레이어입니다. 많은 사용자 정의 드로잉 작업은 draw(_:)를 재정의하지 않고 CALayer 속성을 구성하여 해결할 수 있으며, 이는 훨씬 더 효율적입니다.
Apple Engineering(2024)에 따르면, CALayer 수준의 작업은 GPU에서 실행되는 반면, draw(_:)는 CPU 기반 Core Graphics 렌더링을 통해 작동합니다. 애니메이션 및 부드러운 전환을 위해서는 CALayer 및 CABasicAnimation을 사용하는 것이 좋습니다.
| 시나리오 | 권장 접근 방식 | 성능 |
|---|---|---|
| 둥근 모서리 | layer.cornerRadius | GPU, 높음 |
| 그림자 및 그라데이션 | CAGradientLayer, shadowPath | GPU, 높음 |
| 임의의 도형 | UIBezierPath를 사용한 CAShapeLayer | GPU, 높음 |
| 복잡한 그래픽 | Core Graphics를 사용한 draw(_:) | CPU, 중간 |
| 사용자 정의 서식의 텍스트 | CATextLayer 또는 draw(_:) | 볼륨에 따라 다름 |
애니메이션이 있는 벡터 도형을 그리려면 CAShapeLayer를 사용하세요. 하드웨어 가속이 지원되며 draw(_:)를 호출하지 않고 path, strokeStart 및 strokeEnd 애니메이션을 지원합니다.
Custom UIView의 성능은 애니메이션의 부드러움과 전반적인 사용자 경험에 직접적인 영향을 미칩니다. 주요 문제는 과도한 draw(_:) 호출, 최적이 아닌 하위 뷰 레이아웃 및 캐싱 부족에서 발생합니다.
setNeedsDisplay()를 호출할 때마다 뷰가 완전히 다시 그려집니다. 변경 사항이 뷰의 일부에만 영향을 미치는 경우 특정 사각형과 함께 setNeedsDisplay(_:)를 사용하세요. CALayer 속성(backgroundColor, cornerRadius, shadow)의 경우 다시 그리기가 필요하지 않습니다. GPU 수준에서 업데이트됩니다.
Custom UIView의 콘텐츠가 자주 변경되지 않는 경우 UIGraphicsImageRenderer에서 한 번 렌더링하고 UIImage로 저장하세요. 다음 다시 그리기 시 캐시된 이미지를 표시하려면 draw(at:)를 사용하세요. Core Graphics를 통한 재렌더링보다 수십 배 빠릅니다.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
CALayer의 shouldRasterize 속성은 레이어의 비트맵 표현 캐싱을 활성화합니다. 투명도와 그림자가 있는 정적 뷰에서 이 기능을 활성화하면 합성 부하가 줄어듭니다. 애니메이션 뷰에서는 비활성화하세요. 변경 시마다 캐시가 재설정되며, 래스터화는 성능을 저하시킬 뿐입니다.
자주 묻는 질문
아니요. draw(_:)는 Core Graphics를 통한 사용자 정의 드로잉에만 필요합니다. 뷰가 표준 하위 뷰(UILabel, UIImageView)로 구성되고 CALayer를 사용하는 경우 draw(_:)를 재정의할 필요가 없습니다. 성능이 향상될 수도 있습니다.
캔버스에 일반 UIView를 배치하고 Identity Inspector의 Class 필드에 클래스를 지정합니다. 클래스가 @IBDesignable로 표시된 경우 변경 사항이 Storyboard에 실시간으로 표시됩니다.
init(frame:)은 프로그래밍 방식으로 뷰를 생성할 때 호출되며 위치와 크기가 있는 CGRect를 전달합니다. init(coder:)는 Storyboard 또는 XIB에서 역직렬화할 때 호출됩니다. 올바른 작동을 위해 둘 다 구현해야 합니다. 그렇지 않으면 Interface Builder에서 로드할 때 뷰가 충돌합니다.
가장 일반적인 이유는 뷰에 프레임이 0(너비 또는 높이가 0)이기 때문입니다. 시스템은 0 크기의 뷰에 대해 draw(_:)를 호출하지 않습니다. layoutSubviews()에서 프레임을 확인하고 뷰가 올바른 제약 조건으로 계층 구조에 추가되었는지 확인하세요.
GPU 애니메이션(position, opacity, transform)을 지원하는 속성에는 CALayer를 사용하세요. draw(_:)의 부분 업데이트의 경우 변경된 영역의 CGRect와 함께 setNeedsDisplay(_:)를 사용하세요. 시스템은 전체 뷰가 아닌 지정된 영역만 다시 그립니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.