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 для збереження батьківської логіки малювання, хоча реалізація draw(_:) за замовчуванням у UIView порожня.

Правило ідемпотентності 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 відкидається системою, але витрачає ресурси. Перевіряйте перетин з 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 контекст. Це економить пам'ять. Якщо перевизначення є, але метод не викликається — перевірте, що фрейм в'ю не нульовий і в'ю видима.

Як часто система викликає 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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