CGContext — основният обект на Core Graphics за изпълнение на 2D команди за рисуване в iOS и macOS. Той представлява графичен контекст, който съхранява текущото състояние: цвят на запълване, дебелина на линията, трансформация, зона на изрязване и шрифтове. Всички операции за рисуване — линии, правоъгълници, текст, изображения — се изпълняват чрез CGContext. Според Apple Developer Documentation, 2026, CGContext е основата на цялата растерна и векторна графика в екосистемата на Apple.
Основни моменти
CGContext — непрозрачен тип (opaque type) от рамката Core Graphics, представляващ среда за изпълнение на 2D команди за рендериране. Той управлява стека от състояния, текущото цветово пространство, трансформацията на координатите и зоната на изрязване. CGContext е единственият обект, с който взаимодействат всички функции за рисуване на Quartz 2D — през него преминават всякакви графични операции.
CGContext съществува в няколко разновидности в зависимост от предназначението: bitmap context за работа с растерни изображения в паметта, PDF context за генериране на PDF документи, window context за рендериране на екрана (предоставя се от UIView в draw(_:)). В iOS разработчиците най-често работят с контекста, получен от UIGraphicsGetCurrentContext() вътре в метода draw(_:) или от UIGraphicsBeginImageContextWithOptions за офлайн рендериране.
Контекстът прилага всички настройки чрез стек от състояния — разработчикът може да запази текущото състояние (saveGState), да промени параметри и да възстанови предишното (restoreGState). Това е критично важно при рисуване на сложни сцени, където различни елементи изискват различни цветове, дебелини на линии и трансформации. Според Apple WWDC 2023, правилното използване на стека от състояния подобрява четимостта на кода и предотвратява визуални артефакти.
CGContextSaveGState поставя копие на текущото състояние на контекста в стека. Това състояние включва всички настройки: цветове, дебелина на линията, трансформация, сенки, зона на изрязване, шрифтове. След модифициране на параметрите, извикването на CGContextRestoreGState връща контекста към запазеното състояние, отменяйки всички междинни промени. Стекът може да съдържа до 32 състояния на iOS.
При рисуване на група елементи с различни стилове (например разноцветни сектори на диаграма), стекът от състояния премахва необходимостта от ръчно запомняне и възстановяване на всеки параметър. Без saveGState/restoreGState разработчикът би трябвало да възстановява цвят, сянка и трансформация след всеки елемент. Използването на стека съкращава кода и елиминира грешки при възстановяване.
Важно е да се поддържа баланс на запазванията и възстановяванията — всеки saveGState трябва да има съответен restoreGState. Нарушаването на баланса води до изтичане на състояния или преждевременно възстановяване, което причинява непредвидимо показване. Статичният анализатор на Xcode не проверява баланса на стека на CGContext, затова отговорността за коректност е на разработчика. Препоръчва се групиране на двойките в do/finally блокове.
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
ctx.setStrokeColor(UIColor.red.cgColor)
ctx.setLineWidth(3)
ctx.addRect(CGRect(x: 50, y: 50, width: 200, height: 100))
ctx.strokePath()
ctx.restoreGState()
}
CGContextSetFillColorWithColor задава цвета на запълване за следващите операции. Цветът се предава като CGColorRef, който съдържа информация за цветовото пространство и компонентите на цвета. CGContextSetStrokeColorWithColor работи аналогично за цвета на контура. Apple препоръчва кеширане на често използвани CGColorRef за подобряване на производителността.
CGContextDrawLinearGradient рисува линеен градиент между две точки с указани цветове. CGContextDrawRadialGradient създава радиален градиент, преминаващ от една окръжност към друга. За работа с градиенти първо се създава обект CGGradientRef, съдържащ масив от цветове и позиции на спирания. Градиентите в CGContext поддържат както RGB, така и Grayscale цветови пространства.
Запълването на област с градиент изисква предварително задаване на зона на изрязване. Без clip градиентът ще запълни целия контекст, а не само желаната фигура. Типичен модел: добавяне на път в контекста, извикване на clip за ограничаване на зоната, след това рисуване на градиента. Този модел се използва във всички приложения, където се изисква градиентно запълване на сложни форми, от бутони до фонове.
| Функция | Предназначение | Цветово пространство |
|---|---|---|
| setFillColor | Задава цвят на запълване | Всякакво (чрез CGColor) |
| setStrokeColor | Задава цвят на контур | Всякакво (чрез CGColor) |
| drawLinearGradient | Рисува линеен градиент | RGB или Grayscale |
| drawRadialGradient | Рисува радиален градиент | RGB или Grayscale |
CGContextAddPath добавя предварително създаден CGPathRef в контекста за последващо рендериране. След добавяне на пътя той може да бъде очертан (strokePath), запълнен (fillPath) или да се изпълнят и двете операции. CGContextBeginPath започва нов път, изчиствайки предишния. Контекстът поддържа само един активен път във всеки един момент.
CGContextMoveToPoint премества текущата точка на пътя в указаните координати. CGContextAddLineToPoint рисува права линия от текущата точка до зададената. CGContextAddCurveToPoint добавя кубична крива на Безие, CGContextAddQuadCurveToPoint — квадратична. CGContextClosePath затваря пътя с права линия между последната и първата точка.
За разлика от UIBezierPath, където всички тези методи се извикват върху обекта на пътя, в CGContext те се извикват директно върху контекста. Тази разлика е важна при избора на подход: UIBezierPath е по-удобен за повторно използване на контури, CGContext — за еднократно рисуване с минимални разходи. CGContext също поддържа пунктирани линии чрез CGContextSetLineDash, гъвкави краища чрез CGContextSetLineCap и съединения чрез CGContextSetLineJoin.
let ctx = UIGraphicsGetCurrentContext()!
ctx.beginPath()
ctx.moveTo(x: 50, y: 100)
ctx.addLineTo(x: 200, y: 100)
ctx.addCurveTo(x1: 250, y1: 50, x2: 300, y2: 150, x3: 350, y3: 100)
ctx.setStrokeColor(UIColor.blue.cgColor)
ctx.setLineWidth(5)
ctx.strokePath()
CGContextClip ограничава зоната на рисуване до текущия контур. Всички последващи операции за рисуване ще бъдат видими само вътре в тази зона. Това е основният механизъм за маскиране, заобляне на ъгли и създаване на нестандартни граници на елементи. Зоната на изрязване също се запазва и възстановява чрез saveGState/restoreGState.
CGContextTranslateCTM премества началото на координатите на контекста. CGContextScaleCTM мащабира осите X и Y. CGContextRotateCTM завърта координатната система около текущото начало. Трансформациите се натрупват — всяка следваща се прилага спрямо текущата координатна система. Съставните трансформации (преместване + завъртане + мащаб) се използват за анимация и позициониране на елементи.
Комбинираното използване на изрязване и трансформация предоставя мощни възможности: можете да нарисувате сложна маска чрез изрязване, след което да приложите трансформация към цялото съдържание. Типичен пример — показване на част от изображение под ъгъл със заоблени ръбове. Редът на операциите е важен: първо задайте зона на изрязване, след това приложете трансформация, после рисувайте съдържанието. Apple препоръчва минимизиране на броя промени на CTM (Current Transformation Matrix) в цикъл на рендериране.
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
// зона на изрязване - кръг
ctx.addEllipse(in: CGRect(x: 50, y: 50, width: 200, height: 200))
ctx.clip()
// трансформация на съдържанието
ctx.translateBy(x: 150, y: 150)
ctx.rotate(by: .pi / 4)
ctx.translateBy(x: -150, y: -150)
UIImage(named: "photo")?.draw(at: CGPoint(x: 50, y: 50))
ctx.restoreGState()
}
Нека разгледаме пълен пример за създаване на градиентна диаграма с помощта на CGContext. Диаграмата включва три сектора с различен цвят с градиентно запълване и текстови етикети. Кодът демонстрира комбинацията от saveGState, clip, градиенти и рендериране на текст в един контекст.
class PieChartView: UIView {
private struct Slice {
let color: UIColor
let value: CGFloat
let label: String
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
let slices = [
Slice(color: .systemRed, value: 0.4, label: "iOS"),
Slice(color: .systemGreen, value: 0.35, label: "Android"),
Slice(color: .systemBlue, value: 0.25, label: "Other")
]
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = bounds.width * 0.35
var startAngle: CGFloat = -.pi / 2
for slice in slices {
let endAngle = startAngle + .pi * 2 * slice.value
ctx.saveGState()
ctx.moveTo(x: center.x, y: center.y)
ctx.addArc(center: center, radius: radius,
startAngle: startAngle, endAngle: endAngle, clockwise: false)
ctx.closePath()
ctx.setFillColor(slice.color.cgColor)
ctx.fillPath()
// текстов етикет
let midAngle = startAngle + endAngle / 2
let labelPos = CGPoint(
x: center.x + radius * 0.7 * cos(midAngle),
y: center.y + radius * 0.7 * sin(midAngle)
)
slice.label.draw(at: labelPos, withAttributes: [
.foregroundColor: UIColor.white,
.font: UIFont.boldSystemFont(ofSize: 14)
])
ctx.restoreGState()
startAngle = endAngle
}
}
}
В този пример всеки сектор на диаграмата се рисува в собствен saveGState/restoreGState блок. Този подход гарантира, че настройките на цвят и път за един сектор не влияят на другите. Методът addArc създава дъга от началния до крайния ъгъл, closePath затваря сектора към центъра. Текстовият етикет се поставя на средната линия на ъгъла на сектора с помощта на тригонометричните функции cos и sin за изчисляване на позицията.
За подобряване на производителността при често прерисуване се препоръчва кеширане на изчислението на ъглите и позициите в отделен масив. CGContext изпълнява всички операции на CPU, затова сложни сцени с десетки сектори може да изискват оптимизация — например предварителна растеризация в CGLayer или използване на Metal за GPU ускорение. За повечето UI задачи с 5–10 елемента производителността на CGContext остава приемлива.
Често задавани въпроси
Извикайте UIGraphicsGetCurrentContext() вътре в метода draw(_:) на UIView или вътре в блок на UIGraphicsBeginImageContextWithOptions. За слоеве CALayer използвайте draw(in:) на делегата CALayerDelegate, който получава готов контекст.
fillPath запълва вътрешната област на текущия контур с цвят от setFillColor. strokePath рисува линия по контура с параметрите setStrokeColor, setLineWidth и setLineCap. И двете операции се изпълняват последователно: първо fill, след това stroke при необходимост.
Bitmap CGContext — контекст, създаден чрез CGBitmapContextCreate, който рисува в буфер на паметта вместо на екрана. Използва се за фонова генерация на изображения, обработка на графика без показване и създаване на текстури за Metal или OpenGL.
Core Graphics не предоставя метод за нулиране на всички настройки. Използвайте saveGState преди промени и restoreGState за връщане към първоначалното състояние. Алтернативно можете да създадете нов контекст със същите параметри.
Преди извикване на drawLinearGradient задайте зона на изрязване чрез clip върху желания контур. Без clip градиентът запълва целия контекст. Ред: addPath → clip → drawLinearGradient — гарантира градиент вътре във фигурата.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също