draw(_:) и drawRect — что это, вызов и переопределение

Автор: IT Sectr Опубликовано: 2026-07-20 Время чтения: 7 мин

draw(_:) / drawRect — это метод класса UIView (в Swift) и его аналог в Objective-C (drawRect:), который отвечает за отрисовку содержимого вью с использованием Core Graphics. Система вызывает этот метод автоматически при первом появлении вью на экране и после вызова setNeedsDisplay(). По данным Apple Documentation (2025), draw(_:) является единственным методом, где разработчику доступен графический контекст текущего экрана для кастомного рисования. Переопределение draw(_:) даёт полный контроль над внешним видом компонента — от простых геометрических фигур до сложной анимированной графики.

Главное

  • draw(_:) — метод UIView для кастомной отрисовки через Core Graphics, не вызывается напрямую разработчиком
  • drawRect: — эквивалент draw(_:) в Objective-C, синтаксически отличается, функционально идентичен
  • CGContext — графический контекст, доступный внутри draw(_:) для всех операций рисования
  • setNeedsDisplay() — единственный корректный способ запросить повторный вызов draw(_:) со стороны разработчика
  • UIGraphicsGetCurrentContext() — функция получения текущего контекста внутри draw(_:), обязательный шаг перед рисованием

Что такое draw(_:) / drawRect

draw(_:) — это метод экземпляра UIView, который UIKit вызывает для рендеринга содержимого вью. Внутри этого метода разработчик получает доступ к графическому контексту CGContext и использует API Core Graphics для рисования линий, заливок, текста и изображений. drawRect: в Objective-C выполняет ту же функцию, но с другим синтаксисом: единственный параметр — CGRect, задающий область перерисовки.

По данным Apple Engineering (2024), draw(_:) работает через CPU-рендеринг Bitmap Graphics Context, что обеспечивает максимальную гибкость, но требует больше ресурсов по сравнению с CALayer. Решение об использовании draw(_:) принимается исходя из сложности графики и требований к производительности.

Сигнатура метода в Swift и Objective-C

В Swift метод объявляется как override func draw(_ rect: CGRect), где rect — прямоугольник, который требуется перерисовать. В Objective-C сигнатура - (void)drawRect:(CGRect)rect. Параметр rect может быть меньше bounds вью при частичной перерисовке через setNeedsDisplay(_:).

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(_:).

Как переопределить draw(_:) в Swift

Переопределение draw(_:) в Swift начинается с получения графического контекста и последующих вызовов 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(_:) случайные значения, системное время или сетевые запросы.

drawRect: в Objective-C и разница с 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
Вызов supersuper.draw(rect)[super drawRect:rect]
КонтекстUIGraphicsGetCurrentContext()UIGraphicsGetCurrentContext()
Параметр rectrect: 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 — это Objective-C обёртка над CGPath, которая предоставляет простой API для создания фигур. Создавайте UIBezierPath заранее (например, в инициализаторе) и просто вызывайте fill() или stroke() внутри draw(_:).

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(_:) — UIColor, UIFont и UIGraphicsImageRenderer конструкции внутри draw(_:) создают лишнюю нагрузку на память. Выносите создание объектов в свойства класса.
  • Игнорирование параметра rect — rect указывает область, требующую перерисовки. Отрисовка за пределами rect отбрасывается системой, но расходует ресурсы. Проверяйте intersection с rect перед рисованием.
  • Отсутствие super.draw(rect) — хотя у UIView реализация пуста, Apple рекомендует вызывать super.draw(rect) для совместимости с будущими изменениями UIKit.

Следуя этим правилам, вы обеспечите стабильную и быструю отрисовку кастомных вью в любом проекте под iOS. Профилируйте draw(_:) через Instruments (Core Animation), чтобы увидеть реальное время выполнения и узкие места.

Часто задаваемые вопросы

Можно ли вызывать draw(_:) напрямую?

Нет, прямой вызов draw(_:) запрещён документацией Apple. Система сама управляет циклом отрисовки. Для запроса перерисовки используйте setNeedsDisplay(), который корректно помечает вью как требующую обновления в ближайшем цикле рендеринга.

Чем drawRect: отличается от draw(_:)?

Функционально эти методы идентичны. drawRect: используется в Objective-C, draw(_:) — в Swift. Оба получают CGRect области перерисовки и используют один и тот же Core Graphics контекст через UIGraphicsGetCurrentContext().

Почему draw(_:) не вызывается для пустого UIView?

Apple оптимизирует рендеринг: если у UIView нет переопределённого draw(_:), система не создаёт для него bitmap контекст. Это экономит память. Если переопределение есть, но метод не вызывается — проверьте, что фрейм вью не нулевой и вью visible.

Как часто система вызывает draw(_:)?

Только при необходимости: первый рендеринг, после setNeedsDisplay(), при изменении bounds с contentMode = .redraw. В статичном состоянии draw(_:) не вызывается повторно, что экономит ресурсы CPU и батарею.

Нужно ли вызывать super.draw(rect) в Swift?

Apple рекомендует вызывать super.draw(rect) в начале переопределённого метода. Хотя текущая реализация UIView пуста, super-вызов обеспечивает совместимость с будущими версиями UIKit и является хорошей практикой.

Итоги

  • draw(_:) / drawRect — основной метод отрисовки UIView в Swift и Objective-C, вызывается системой автоматически
  • CGContext — графический контекст Core Graphics, доступный внутри draw(_:) через UIGraphicsGetCurrentContext()
  • setNeedsDisplay() — корректный способ запросить перерисовку; прямой вызов draw(_:) запрещён
  • Иденпотентность — draw(_:) должен давать одинаковый результат при одинаковых входных данных для корректного кэширования
  • UIBezierPath — создавайте пути за пределами draw(_:) для минимизации нагрузки на CPU при каждом вызове
  • Параметр rect — содержит область перерисовки; используйте intersection для оптимизации — не рисуйте за его пределами
  • Профилирование — проверяйте производительность draw(_:) через Instruments Core Animation для выявления медленных операций

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также