draw(_:) и drawRect — какво е това, извикване и предефиниране

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

draw(_:) / drawRect — това е методът на класа UIView (в Swift) и неговият аналог в Objective-C (drawRect:), който отговаря за изобразяването на съдържанието на view с помощта на Core Graphics. Системата извиква този метод автоматично при първата поява на view на екрана и след извикване на 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 извиква за изобразяване на съдържанието на view. Вътре в този метод разработчикът получава достъп до графичния контекст 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 на view при частично прерисуване чрез setNeedsDisplay(_:).

objective-c
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);
}

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

Системата автоматично извиква draw(_:) в строго определени сценарии. Разбирането на тези тригери помага да се избегнат излишни прерисувания и да се оптимизира производителността на view. По-долу са изброени всички случаи на автоматично извикване на метода.

  • Първо изобразяване — когато view за първи път се добавя в йерархията и става видимо на екрана
  • setNeedsDisplay() — след извикване на този метод от системата в най-близкия цикъл на рисуване
  • setNeedsDisplay(_:) — същото, но с посочване на конкретен правоъгълник за прерисуване
  • contentMode — при промяна на bounds, ако contentMode изисква прерисуване (напр. .redraw)
  • setNeedsLayout() — в някои случаи след пренареждане на subview може да се наложи прерисуване

Документацията на 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 }

    // Параметри на линия
    context.setStrokeColor(UIColor.darkGray.cgColor)
    context.setLineWidth(2.0)

    // Рисуване на триъгълник
    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(_:) отново, ако съдържанието на view не се е променило. Не използвайте вътре в 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()
    // Изграждане на форма на звезда
    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.

Следвайки тези правила, ще осигурите стабилно и бързо рисуване на персонализирани view-та във всеки iOS проект. Профилирайте draw(_:) чрез Instruments (Core Animation), за да видите реалното време за изпълнение и тесните места.

Често задавани въпроси

Може ли draw(_:) да се извиква директно?

Не, директното извикване на draw(_:) е забранено от документацията на Apple. Системата сама управлява цикъла на рисуване. За заявка за прерисуване използвайте setNeedsDisplay(), който правилно маркира view като изискващо обновяване в най-близкия цикъл на рендеринг.

С какво drawRect: се различава от draw(_:)?

Функционално тези методи са идентични. drawRect: се използва в Objective-C, draw(_:) — в Swift. И двата получават CGRect на зоната за прерисуване и използват един и същ Core Graphics контекст чрез UIGraphicsGetCurrentContext().

Защо draw(_:) не се извиква за празен UIView?

Apple оптимизира рендеринга: ако UIView няма предефиниран draw(_:), системата не създава bitmap контекст за него. Това спестява памет. Ако предефинирането съществува, но методът не се извиква — проверете дали рамката на view не е нулева и view е видимо.

Колко често системата извиква 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също