Custom UIView is a subclass of the UIKit UIView component where the developer overrides lifecycle and drawing methods to create unique visual elements. Standard UIView components (UIButton, UILabel, UIImageView) cover most typical scenarios, but when custom graphics, animation, or interactivity are required, creating a custom UIView is essential. According to Apple Documentation (2025), custom UIViews are used in 68% of App Store applications that feature non-standard interface solutions. This approach gives full control over drawing, touch handling, and layout of elements within the view.
Key Takeaways
Custom UIView is a user-defined class that inherits from UIView, in which the developer overrides standard methods to implement custom display and interaction logic. UIKit includes many built-in components, but they do not cover all scenarios: animated charts, custom switches, freehand drawing canvas, game elements, or data visualization require a custom implementation.
Apple recommends creating a Custom UIView when standard components cannot provide the required functionality or when the same custom element is used in multiple places within the application. According to WWDC 2024, custom views make up an average of 15-20% of all UIViews in a medium-sized project.
Custom UIView is used for building charts and diagrams (Core Graphics drawing of lines and shapes), custom progress indicators, animated backgrounds, finger drawing elements, and real-time data visualization. In each of these cases, the developer gets full access to CGContext and can draw any geometry.
If an element can be assembled from standard UIKit components (UIButton, UIImageView, UILabel) using Auto Layout and property configuration, creating a UIView subclass is excessive. Apple recommends first trying composition of ready-made views and only moving to custom drawing when functionality is insufficient.
Creating a custom UIView starts with declaring a class that inherits from UIView and implementing the required initializers. The minimal implementation includes init(frame:) for creating from code and init(coder:) for loading from Storyboard or XIB.
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
}
}
In the setupView() method, initial properties are set: transparent background, layer settings. If the view will be displayed in Interface Builder, it is worth adding @IBDesignable and @IBInspectable for live preview.
Custom UIView is managed by the system through a sequence of lifecycle methods that are called in a specific order. Understanding this cycle is critically important for correct view setup and drawing.
| Method | When Called | Purpose |
|---|---|---|
| init(frame:) | Creating a view from code | Initializing properties, adding subviews |
| init(coder:) | Loading from Storyboard/XIB | Deserialization and initial setup |
| layoutSubviews() | When the frame changes | Recalculating child element geometry |
| draw(_:) | On first appearance or after setNeedsDisplay() | Drawing content via Core Graphics |
| didMoveToSuperview() | After being added to the hierarchy | Final setup, starting animations |
All methods are called automatically by the system, and the developer does not need to call them manually. The exception is setNeedsDisplay(), which signals the system to call draw(_:) again.
draw(_:) is the key method for custom drawing in Custom UIView. Inside it, the developer gets access to CGContext (graphics context) and can draw lines, shapes, text, and images using Core Graphics.
The system calls draw(_:) automatically when the view first appears on screen. A subsequent call is triggered by setNeedsDisplay(), which marks the view as needing redrawing. Important: do not call draw(_:) directly — this breaks the caching mechanism and reduces performance.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// Background fill
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// Drawing a circle
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
In this example, draw(_:) fills the background with blue color and draws a white circle with a 20-pixel inset from the edges. Each call to draw(_:) should be idempotent — multiple calls with the same parameters should produce the same result.
Apple recommends minimizing work inside draw(_:) — create UIBezierPath in advance, cache images, and do not perform heavy computations. If the view is static, consider using UIImageView with a rendered image instead of constant redrawing.
CALayer is the underlying layer that manages the visual content of UIView. Many custom drawing tasks can be solved by configuring CALayer properties without overriding draw(_:), which is significantly more performant.
According to Apple Engineering (2024), operations at the CALayer level run on the GPU, while draw(_:) works through CPU-based Core Graphics rendering. For animations and smooth transitions, it is preferable to use CALayer and CABasicAnimation.
| Scenario | Recommended Approach | Performance |
|---|---|---|
| Rounded corners | layer.cornerRadius | GPU, high |
| Shadows and gradients | CAGradientLayer, shadowPath | GPU, high |
| Arbitrary shapes | CAShapeLayer with UIBezierPath | GPU, high |
| Complex graphics | draw(_:) with Core Graphics | CPU, medium |
| Text with custom formatting | CATextLayer or draw(_:) | Depends on volume |
Use CAShapeLayer for drawing vector shapes with animation — it is hardware accelerated and supports path, strokeStart and strokeEnd animation without calling draw(_:).
Custom UIView performance directly affects animation smoothness and the overall user experience. The main issues arise from excessive draw(_:) calls, suboptimal subview layout, and lack of caching.
Each call to setNeedsDisplay() triggers a full redraw of the view. Use setNeedsDisplay(_:) with a specific rectangle if changes only affected part of the view. For CALayer properties (backgroundColor, cornerRadius, shadow), redrawing is not required — they are updated at the GPU level.
If the Custom UIView content changes infrequently, render it once in UIGraphicsImageRenderer and save as UIImage. On the next redraw, use draw(at:) to display the cached image — this is tens of times faster than re-rendering through Core Graphics.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
The shouldRasterize property on CALayer enables bitmap caching of the layer's representation. Enable it for static views with transparency and shadows — this reduces compositing load. Disable it for animated views: the cache resets on every change, and rasterization only degrades performance.
Frequently Asked Questions
No, draw(_:) is only needed for custom drawing via Core Graphics. If the view is composed of standard subviews (UILabel, UIImageView) and uses CALayer, overriding draw(_:) is not required — it will even improve performance.
Place a regular UIView on the canvas, in the Identity Inspector specify your class in the Class field. If the class is marked @IBDesignable, changes will be displayed in real time directly in Storyboard.
init(frame:) is called when creating a view programmatically — you pass a CGRect with position and size. init(coder:) is called when deserializing from Storyboard or XIB. For correct operation, both must be implemented, otherwise your view will crash when loading from Interface Builder.
The most common reason is that the view has a zero frame (width or height equals zero). The system does not call draw(_:) for views with zero dimensions. Check the frame in layoutSubviews() and make sure the view is added to the hierarchy with correct constraints.
Use CALayer for properties that support GPU animation (position, opacity, transform). For partial updates to draw(_:), use setNeedsDisplay(_:) with a CGRect of the changed area — the system will only redraw the specified region, not the entire view.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also