CGContext в iOS — какво е, методи и как да управлявате контекста на рисуване

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

CGContext — основният обект на Core Graphics за изпълнение на 2D команди за рисуване в iOS и macOS. Той представлява графичен контекст, който съхранява текущото състояние: цвят на запълване, дебелина на линията, трансформация, зона на изрязване и шрифтове. Всички операции за рисуване — линии, правоъгълници, текст, изображения — се изпълняват чрез CGContext. Според Apple Developer Documentation, 2026, CGContext е основата на цялата растерна и векторна графика в екосистемата на Apple.

Основни моменти

  • CGContext — графичен контекст, чрез който се изпълняват всички 2D команди на Core Graphics.
  • setStrokeColor и setFillColor управляват цвета на линиите и запълванията в контекста.
  • addPath и drawPath позволяват рисуване на произволни CGPath контури.
  • clip(to:) задава зона на изрязване за ограничаване на рисуването в определена зона.
  • saveGState и restoreGState запазват и възстановяват стека от състояния на контекста.

Какво е CGContext?

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, правилното използване на стека от състояния подобрява четимостта на кода и предотвратява визуални артефакти.

Управление на състоянието: saveGState и restoreGState

CGContextSaveGState поставя копие на текущото състояние на контекста в стека. Това състояние включва всички настройки: цветове, дебелина на линията, трансформация, сенки, зона на изрязване, шрифтове. След модифициране на параметрите, извикването на CGContextRestoreGState връща контекста към запазеното състояние, отменяйки всички междинни промени. Стекът може да съдържа до 32 състояния на iOS.

Практическо приложение на стека

При рисуване на група елементи с различни стилове (например разноцветни сектори на диаграма), стекът от състояния премахва необходимостта от ръчно запомняне и възстановяване на всеки параметър. Без saveGState/restoreGState разработчикът би трябвало да възстановява цвят, сянка и трансформация след всеки елемент. Използването на стека съкращава кода и елиминира грешки при възстановяване.

Важно е да се поддържа баланс на запазванията и възстановяванията — всеки saveGState трябва да има съответен restoreGState. Нарушаването на баланса води до изтичане на състояния или преждевременно възстановяване, което причинява непредвидимо показване. Статичният анализатор на Xcode не проверява баланса на стека на CGContext, затова отговорността за коректност е на разработчика. Препоръчва се групиране на двойките в do/finally блокове.

swift
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 за подобряване на производителността.

Градиенти в CGContext

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.

swift
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) в цикъл на рендериране.

swift
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

Нека разгледаме пълен пример за създаване на градиентна диаграма с помощта на CGContext. Диаграмата включва три сектора с различен цвят с градиентно запълване и текстови етикети. Кодът демонстрира комбинацията от saveGState, clip, градиенти и рендериране на текст в един контекст.

swift
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 остава приемлива.

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

Как да получите текущия CGContext в iOS?

Извикайте UIGraphicsGetCurrentContext() вътре в метода draw(_:) на UIView или вътре в блок на UIGraphicsBeginImageContextWithOptions. За слоеве CALayer използвайте draw(in:) на делегата CALayerDelegate, който получава готов контекст.

Каква е разликата между fillPath и strokePath?

fillPath запълва вътрешната област на текущия контур с цвят от setFillColor. strokePath рисува линия по контура с параметрите setStrokeColor, setLineWidth и setLineCap. И двете операции се изпълняват последователно: първо fill, след това stroke при необходимост.

Какво е bitmap CGContext?

Bitmap CGContext — контекст, създаден чрез CGBitmapContextCreate, който рисува в буфер на паметта вместо на екрана. Използва се за фонова генерация на изображения, обработка на графика без показване и създаване на текстури за Metal или OpenGL.

Как да нулирате всички настройки на CGContext?

Core Graphics не предоставя метод за нулиране на всички настройки. Използвайте saveGState преди промени и restoreGState за връщане към първоначалното състояние. Алтернативно можете да създадете нов контекст със същите параметри.

Защо градиентът не се показва вътре във фигурата?

Преди извикване на drawLinearGradient задайте зона на изрязване чрез clip върху желания контур. Без clip градиентът запълва целия контекст. Ред: addPath → clip → drawLinearGradient — гарантира градиент вътре във фигурата.

Резюме

  • CGContext — централният обект на Core Graphics за всички 2D операции за рисуване на iOS и macOS.
  • saveGState и restoreGState управляват стека от състояния на контекста, предотвратявайки странични ефекти.
  • setFillColor и setStrokeColor задават цветове за запълване и контур чрез CGColorRef.
  • addPath, strokePath, fillPath работят с произволни CGPath контури.
  • clip ограничава зоната на рисуване до текущия контур за маски и заобляния.
  • translateCTM, scaleCTM, rotateCTM трансформират координатната система на контекста.
  • Всички операции на CGContext се изпълняват на CPU — за сложна графика използвайте CGLayer или Metal.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

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

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