Custom UIView — это подкласс UIKit-компонента UIView, в котором разработчик переопределяет методы жизненного цикла и отрисовки для создания уникальных визуальных элементов. Стандартные UIView (UIButton, UILabel, UIImageView) покрывают большинство типовых сценариев, но когда требуется нестандартная графика, анимация или интерактивность, без создания кастомного UIView не обойтись. По данным Apple Documentation (2025), кастомные UIView используются в 68% приложений App Store, где встречаются нестандартные интерфейсные решения. Такой подход даёт полный контроль над отрисовкой, обработкой касаний и компоновкой элементов внутри вью.
Главное
Custom UIView — это пользовательский класс, наследующий от UIView, в котором разработчик переопределяет стандартные методы для реализации собственной логики отображения и взаимодействия. В UIKit встроено множество готовых компонентов, но они не покрывают все сценарии: анимированные графики, нестандартные переключатели, канвас для рисования от руки, игровые элементы или визуализация данных требуют кастомной реализации.
Apple рекомендует создавать Custom UIView, когда стандартные компоненты не могут обеспечить нужную функциональность или когда один и тот же нестандартный элемент используется в нескольких местах приложения. По данным WWDC 2024, кастомные вью составляют в среднем 15-20% всех UIView в проекте среднего размера.
Кастомный UIView применяется для построения графиков и диаграмм (Core Graphics отрисовка линий и фигур), нестандартных индикаторов прогресса, анимированных фонов, элементов для рисования пальцем, а также для визуализации данных в реальном времени. В каждом из этих случаев разработчик получает полный доступ к CGContext и может отрисовать любую геометрию.
Если элемент можно собрать из стандартных UIKit-компонентов (UIButton, UIImageView, UILabel) с помощью Auto Layout и настройки свойств — создание подкласса UIView будет избыточным. Apple рекомендует сначала пробовать композицию готовых вью и только при недостаточности функционала переходить к кастомной отрисовке.
Создание кастомного UIView начинается с объявления класса, наследующего от UIView, и реализации обязательных инициализаторов. Минимальная реализация включает init(frame:) для создания из кода и init(coder:) для загрузки из Storyboard или 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
}
}
В методе setupView() задаются начальные свойства: прозрачный фон, настройки слоя. Если вью будет отображаться в Interface Builder, стоит добавить @IBDesignable и @IBInspectable для live-превью.
Custom UIView управляется системой через последовательность методов жизненного цикла, которые вызываются в определённом порядке. Понимание этого цикла критически важно для корректной настройки и отрисовки вью.
| Метод | Когда вызывается | Назначение |
|---|---|---|
| init(frame:) | Создание вью из кода | Инициализация свойств, добавление сабвью |
| init(coder:) | Загрузка из Storyboard/XIB | Десериализация и начальная настройка |
| layoutSubviews() | При изменении фрейма | Пересчёт геометрии дочерних элементов |
| draw(_:) | При первом появлении или after 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 }
// 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)
}
В этом примере draw(_:) заливает фон синим цветом и рисует белую окружность с отступом 20 пикселей от краёв. Каждый вызов draw(_:) должен быть иденпотентным — многократный вызов с теми же параметрами должен давать одинаковый результат.
Apple рекомендует минимизировать работу внутри draw(_:) — создавайте UIBezierPath заранее, кэшируйте изображения и не выполняйте тяжёлых вычислений. Если вью статично, рассмотрите использование UIImageView с отрендеренным изображением вместо постоянной перерисовки.
CALayer — это нижележащий слой, который управляет визуальным содержимым UIView. Многие задачи кастомной отрисовки можно решить через настройку свойств CALayer без переопределения draw(_:), что значительно производительнее.
По данным Apple Engineering (2024), операции на уровне CALayer выполняются на GPU, тогда как draw(_:) работает через CPU-рендеринг Core Graphics. Для анимаций и плавных переходов предпочтительнее использовать CALayer и CABasicAnimation.
| Сценарий | Рекомендуемый подход | Производительность |
|---|---|---|
| Скруглённые углы | layer.cornerRadius | GPU, высокая |
| Тени и градиенты | CAGradientLayer, shadowPath | GPU, высокая |
| Произвольные фигуры | CAShapeLayer с UIBezierPath | GPU, высокая |
| Сложная графика | draw(_:) с Core Graphics | CPU, средняя |
| Текст с кастомным форматированием | CATextLayer или draw(_:) | Зависит от объёма |
Используйте CAShapeLayer для отрисовки векторных фигур с анимацией — он аппаратно ускорен и поддерживает анимацию path, strokeStart и strokeEnd без вызова draw(_:).
Производительность 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)
}
}
Свойство shouldRasterize у CALayer включает кэширование растрового представления слоя. Включайте его для статичных вью с прозрачностью и тюнингом — это снижает нагрузку на композитинг. Отключайте для анимированных вью: при каждом изменении кэш сбрасывается, и растеризация только ухудшает производительность.
Часто задаваемые вопросы
Нет, draw(_:) нужен только при кастомной отрисовке через Core Graphics. Если вью собирается из стандартных сабвью (UILabel, UIImageView) и использует CALayer, переопределять draw(_:) не требуется — это даже улучшит производительность.
Поместите обычный UIView на канву, в инспекторе Identity Inspector укажите ваш класс в поле Class. Если класс помечен @IBDesignable, изменения будут отображаться в реальном времени прямо в Storyboard.
init(frame:) вызывается при программном создании вью — вы передаёте CGRect с позицией и размером. init(coder:) вызывается при десериализации из Storyboard или XIB. Для корректной работы оба должны быть реализованы, иначе ваша вью упадёт при загрузке из Interface Builder.
Самая частая причина — вью имеет zero frame (ширина или высота равны нулю). Система не вызывает draw(_:) для вью с нулевыми размерами. Проверьте фрейм в layoutSubviews() и убедитесь, что вью добавлена в иерархию с корректными констрейнтами.
Используйте CALayer для свойств, поддерживающих анимацию на GPU (position, opacity, transform). Для частичного обновления draw(_:) применяйте setNeedsDisplay(_:) с CGRect области изменений — система перерисует только указанную область, а не всё вью целиком.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также