Custom UIView:是什么、创建方法及重写 drawRect

作者: IT Sectr 发布日期: 2026-07-20 阅读时间: 7 分钟

Custom UIView — 是 UIKit 组件 UIView 的子类,开发者在其中重写生命周期和绘制方法以创建独特的视觉元素。标准 UIView(UIButton、UILabel、UIImageView)覆盖了大多数典型场景,但当需要非标准图形、动画或交互性时,不创建自定义 UIView 就无法实现。根据 Apple Documentation(2025),在 App Store 中遇到非标准界面解决方案的应用程序中有 68% 使用了自定义 UIView。这种方法提供了对绘制、触摸处理和视图内元素布局的完全控制。

要点

  • Custom UIView — 具有用于自定义绘制和行为的重写方法的 UIView 子类
  • draw(_:) — 主要绘制方法,在视图首次出现在屏幕上时由系统调用
  • init(frame:)init(coder:) — 从代码和 Storyboard 创建视图的必需初始化器
  • layoutSubviews() — 在视图大小改变时调用,允许重新计算子元素的几何结构
  • CALayer — 底层图层,通过它可以在不重写 draw(_:) 的情况下优化绘制

什么是 Custom UIView 以及何时需要

Custom UIView — 是一个继承自 UIView 的自定义类,开发者在此重写标准方法以实现自己的显示和交互逻辑。UIKit 包含许多现成组件,但它们无法覆盖所有场景:动画图表、非标准开关、手绘画布、游戏元素或数据可视化都需要自定义实现。

Apple 建议在标准组件无法提供所需功能,或同一自定义元素在应用程序的多个位置使用时创建 Custom UIView。根据 WWDC 2024 的数据,在中型项目中,自定义视图平均占所有 UIView 的 15-20%。

典型使用场景

自定义 UIView 用于构建图表和示意图(使用 Core Graphics 绘制线条和形状)、非标准进度指示器、动画背景、手指绘制元素,以及实时数据可视化。在所有这些情况下,开发者都可以完全访问 CGContext 并绘制任何几何图形。

何时不需要 Custom UIView

如果元素可以使用 Auto Layout 和属性配置从标准 UIKit 组件(UIButton、UIImageView、UILabel)组装而成,那么创建 UIView 子类将是多余的。Apple 建议先尝试组合现成视图,只有在功能不足时才转向自定义绘制。

如何在 Xcode 中创建 Custom UIView

创建自定义 UIView 从声明继承自 UIView 的类并实现必需的初始化器开始。最小实现包括用于从代码创建的 init(frame:) 和用于从 Storyboard 或 XIB 加载的 init(coder:)

swift
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 以实现实时预览。

UIView 生命周期方法

Custom UIView 由系统通过按特定顺序调用的生命周期方法序列进行管理。理解这个周期对于正确配置和绘制视图至关重要。

方法何时调用目的
init(frame:)从代码创建视图初始化属性,添加子视图
init(coder:)从 Storyboard/XIB 加载反序列化和初始配置
layoutSubviews()框架改变时重新计算子元素的几何结构
draw(_:)首次出现时或 setNeedsDisplay() 后通过 Core Graphics 绘制内容
didMoveToSuperview()添加到层次结构后最终配置,启动动画

所有方法都由系统自动调用,开发者无需手动调用它们。例外是 setNeedsDisplay(),它向系统发出需要再次调用 draw(_:) 的信号。

重写 draw(_:) 进行绘制

draw(_:) — 是 Custom UIView 中自定义绘制的关键方法。在其中,开发者可以访问 CGContext(图形上下文)并使用 Core Graphics 绘制线条、形状、文本和图像。

系统在视图首次出现在屏幕上时自动调用 draw(_:)。通过 setNeedsDisplay() 发起重新调用,该方法将视图标记为需要重绘。重要提示:不要直接调用 draw(_:) —— 这会破坏缓存机制并降低性能。

swift
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(_:) 调用都应该是幂等的 —— 使用相同参数的重复调用应产生相同的结果。

高效 draw(_:) 的规则

Apple 建议最小化 draw(_:) 内部的工作 —— 提前创建 UIBezierPath,缓存图像,不执行繁重计算。如果视图是静态的,考虑使用带有渲染图像的 UIImageView,而不是不断重绘。

CALayer 对比 draw(_:):如何选择

CALayer — 是管理 UIView 视觉内容的底层图层。许多自定义绘制任务可以通过配置 CALayer 属性而不重写 draw(_:) 来解决,这要高效得多。

根据 Apple Engineering(2024),CALayer 级别的操作在 GPU 上执行,而 draw(_:) 通过 CPU 的 Core Graphics 渲染工作。对于动画和平滑过渡,更推荐使用 CALayer 和 CABasicAnimation。

场景推荐方法性能
圆角layer.cornerRadiusGPU,高
阴影和渐变CAGradientLayer, shadowPathGPU,高
任意形状结合 UIBezierPath 使用 CAShapeLayerGPU,高
复杂图形结合 Core Graphics 使用 draw(_:)CPU,中等
自定义格式文本CATextLayer 或 draw(_:)取决于数量

使用 CAShapeLayer 绘制带动画的矢量形状 —— 它由硬件加速,并支持在无需调用 draw(_:) 的情况下对 path、strokeStart 和 strokeEnd 进行动画。

Custom UIView 性能优化

Custom UIView 的性能直接影响动画的流畅度和应用程序的整体用户体验。主要问题源于过多的 draw(_:) 调用、子视图布局不优化以及缺乏缓存。

避免不必要的重绘

每次 setNeedsDisplay() 调用都会导致视图完全重绘。如果更改只影响了视图的一部分,请使用指定了具体矩形的 setNeedsDisplay(_:)。对于 CALayer 属性(backgroundColor、cornerRadius、shadow),无需重绘 —— 它们在 GPU 级别更新。

缓存渲染内容

如果 Custom UIView 的内容很少更改,请在 UIGraphicsImageRenderer 中渲染一次并保存为 UIImage。在下一次重绘时,使用 draw(at:) 显示缓存的图像 —— 这比通过 Core Graphics 重新绘制快数十倍。

swift
func renderToImage() -> UIImage {
    let renderer = UIGraphicsImageRenderer(size: bounds.size)
    return renderer.image { ctx in
        drawHierarchy(in: bounds, afterScreenUpdates: true)
    }
}

对静态图层使用 shouldRasterize

CALayer 的 shouldRasterize 属性启用图层光栅表示形式的缓存。为具有透明度和阴影的静态视图启用它 —— 这减少了合成负载。为动画视图禁用它:每次更改时缓存都会重置,光栅化只会降低性能。

常见问题

是否必须在 Custom UIView 中重写 draw(_:)?

不需要,draw(_:) 仅在通过 Core Graphics 进行自定义绘制时才需要。如果视图由标准子视图(UILabel、UIImageView)组成并使用 CALayer,则不需要重写 draw(_:) —— 这甚至会提高性能。

如何在 Storyboard 中添加 Custom UIView?

在画布上放置一个普通的 UIView,在 Identity Inspector 的 Class 字段中指定您的类。如果类标记有 @IBDesignable,更改将直接实时显示在 Storyboard 中。

init(frame:) 和 init(coder:) 有什么区别?

init(frame:) 在编程创建视图时调用 —— 您传递一个包含位置和大小的 CGRect。init(coder:) 在从 Storyboard 或 XIB 反序列化时调用。为了正常工作,必须实现两者,否则视图在从 Interface Builder 加载时会崩溃。

为什么 draw(_:) 没有被调用?

最常见的原因是视图具有零框架(宽度或高度为零)。系统不会为零尺寸的视图调用 draw(_:)。在 layoutSubviews() 中检查框架,并确保视图已使用正确约束添加到层次结构中。

如何在完全不重绘的情况下更新 Custom UIView 的内容?

对于支持 GPU 动画的属性(position、opacity、transform),请使用 CALayer。对于 draw(_:) 的部分更新,使用带有更改区域 CGRect 的 setNeedsDisplay(_:) —— 系统将仅重绘指定区域,而不是整个视图。

总结

  • Custom UIView — 具有用于自定义绘制的重写方法的 UIView 子类,在标准 UIKit 组件不足时使用
  • draw(_:) — 通过 Core Graphics 进行自定义绘制的方法,由系统自动调用;禁止直接调用
  • CALayer — GPU 加速图形(阴影、圆角、形状)的首选方法,是 draw(_:) 的替代方案,性能更高
  • init(frame:)init(coder:) — 必需的初始化器,两者都必须实现才能从代码和 Storyboard 正常工作
  • setNeedsDisplay() — 向系统发出再次调用 draw(_:) 的信号;使用带有 CGRect 的重载进行部分重绘
  • @IBDesignable — 用于在 Interface Builder 中实时预览 Custom UIView 的属性,简化视觉开发
  • 对于静态图形,通过 UIGraphicsImageRenderer 缓存结果并绘制准备好的图像 —— 这减轻了 CPU 负担

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读