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 (поточної матриці трансформації) в циклі малювання.
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
// clipping region - circle
ctx.addEllipse(in: CGRect(x: 50, y: 50, width: 200, height: 200))
ctx.clip()
// content transformation
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()
// text label
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. Обидві операції виконуються послідовно: спочатку заливка, потім обведення при необхідності.
Bitmap CGContext — контекст, створений через CGBitmapContextCreate, який малює в буфер пам'яті замість екрана. Використовується для фонової генерації зображень, обробки графіки без відображення та створення текстур для Metal або OpenGL.
Core Graphics не надає методу скидання всіх налаштувань. Використовуйте saveGState перед змінами та restoreGState для повернення до початкового стану. Альтернативно можна створити новий контекст з тими ж параметрами.
Перед викликом drawLinearGradient встановіть область відсікання через clip на потрібний контур. Без clip градієнт заповнює весь контекст. Порядок: addPath → clip → drawLinearGradient — гарантує градієнт всередині фігури.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також