draw(_:) / drawRect — това е методът на класа UIView (в Swift) и неговият аналог в Objective-C (drawRect:), който отговаря за изобразяването на съдържанието на view с помощта на Core Graphics. Системата извиква този метод автоматично при първата поява на view на екрана и след извикване на setNeedsDisplay(). Според Apple Documentation (2025), draw(_:) е единственият метод, в който разработчикът има достъп до графичния контекст на текущия екран за персонализирано рисуване. Предефинирането на draw(_:) дава пълен контрол върху външния вид на компонента — от прости геометрични фигури до сложна анимирана графика.
Основни моменти
draw(_:) — е инстанционен метод на UIView, който UIKit извиква за изобразяване на съдържанието на view. Вътре в този метод разработчикът получава достъп до графичния контекст CGContext и използва API на Core Graphics за рисуване на линии, запълвания, текст и изображения. drawRect: в Objective-C изпълнява същата функция, но с различен синтаксис: единственият параметър е CGRect, задаващ зоната за прерисуване.
Според Apple Engineering (2024), draw(_:) работи чрез CPU-рендеринг на Bitmap Graphics Context, което осигурява максимална гъвкавост, но изисква повече ресурси в сравнение с CALayer. Решението за използване на draw(_:) се взема въз основа на сложността на графиката и изискванията за производителност.
В Swift методът се декларира като override func draw(_ rect: CGRect), където rect е правоъгълникът, който трябва да бъде прерисуван. В Objective-C сигнатурата е - (void)drawRect:(CGRect)rect. Параметърът rect може да бъде по-малък от bounds на view при частично прерисуване чрез setNeedsDisplay(_:).
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
Системата автоматично извиква draw(_:) в строго определени сценарии. Разбирането на тези тригери помага да се избегнат излишни прерисувания и да се оптимизира производителността на view. По-долу са изброени всички случаи на автоматично извикване на метода.
Документацията на Apple предупреждава: никога не извиквайте draw(_:) директно. Системата сама решава кога да извърши рисуването, а директното извикване нарушава вътрешния механизъм за кеширане. За да поискате прерисуване, винаги използвайте setNeedsDisplay() или setNeedsDisplay(_:).
Предефинирането на draw(_:) в Swift започва с получаване на графичния контекст и последващи извиквания на Core Graphics. Препоръчва се създаване на отделни методи за логически блокове за рисуване — това подобрява четимостта и тестваемостта на кода.
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(_:) отново, ако съдържанието на view не се е променило. Не използвайте вътре в 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 |
| Извикване на super | super.draw(rect) | [super drawRect:rect] |
| Контекст | UIGraphicsGetCurrentContext() | UIGraphicsGetCurrentContext() |
| Параметър rect | rect: CGRect | CGRect rect |
| Производителност | Идентична | Идентична |
При миграция на проект от Objective-C към Swift, преименуването на метода е една от първите задачи. Xcode предоставя автоматичен конвертор, но drawRect: изисква ръчно обновяване до draw(_:). Според Apple (2024), Swift версията draw(_:) е предпочитана за нови проекти.
draw(_:) се изпълнява на CPU и неоптималната имплементация може да причини спад на кадрите и ниска производителност. Apple препоръчва няколко доказани подхода за ускоряване на рисуването.
Всяка операция на Core Graphics (move(to:), addLine(to:), strokePath) има допълнителни разходи. Групирайте операциите и използвайте CGPath за сложни фигури — пътят се създава веднъж и се преизползва при всяко извикване на draw(_:).
UIBezierPath — е Objective-C обвивка около CGPath, която предоставя прост API за създаване на фигури. Създайте UIBezierPath предварително (напр. в инициализатора) и просто извиквайте fill() или stroke() вътре в draw(_:).
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(_:). Познаването на тези модели помага да се избегнат грешки и намаляване на производителността. Нека разгледаме най-често срещаните проблеми и техните решения.
Следвайки тези правила, ще осигурите стабилно и бързо рисуване на персонализирани view-та във всеки iOS проект. Профилирайте draw(_:) чрез Instruments (Core Animation), за да видите реалното време за изпълнение и тесните места.
Често задавани въпроси
Не, директното извикване на draw(_:) е забранено от документацията на Apple. Системата сама управлява цикъла на рисуване. За заявка за прерисуване използвайте setNeedsDisplay(), който правилно маркира view като изискващо обновяване в най-близкия цикъл на рендеринг.
Функционално тези методи са идентични. drawRect: се използва в Objective-C, draw(_:) — в Swift. И двата получават CGRect на зоната за прерисуване и използват един и същ Core Graphics контекст чрез UIGraphicsGetCurrentContext().
Apple оптимизира рендеринга: ако UIView няма предефиниран draw(_:), системата не създава bitmap контекст за него. Това спестява памет. Ако предефинирането съществува, но методът не се извиква — проверете дали рамката на view не е нулева и view е видимо.
Само при необходимост: първо изобразяване, след setNeedsDisplay(), при промяна на bounds с contentMode = .redraw. В статично състояние draw(_:) не се извиква отново, което спестява ресурси на CPU и батерията.
Apple препоръчва извикване на super.draw(rect) в началото на предефинирания метод. Въпреки че текущата имплементация на UIView е празна, извикването на super осигурява съвместимост с бъдещи версии на UIKit и е добра практика.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също