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()

    // 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

Рассмотрим полный пример создания градиентной диаграммы с использованием 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()

            // 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 остаётся приемлемой.

Часто задаваемые вопросы

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

Обсудить проект

Читайте также