CGContext is the primary Core Graphics object for executing 2D drawing commands in iOS and macOS. It represents a graphics context that stores the current state: fill color, line thickness, transformation, clipping area, and fonts. All drawing operations — lines, rectangles, text, images — are performed through CGContext. According to Apple Developer Documentation, 2026, CGContext is the foundation of all raster and vector graphics in the Apple ecosystem.
Key Takeaways
CGContext is an opaque type from the Core Graphics framework that represents an environment for executing 2D rendering commands. It manages the state stack, current color space, coordinate transformation, and clipping area. CGContext is the only object that all Quartz 2D drawing functions interact with — any graphics operation passes through it.
CGContext comes in several varieties depending on its purpose: bitmap context for working with raster images in memory, PDF context for generating PDF documents, window context for on-screen drawing (provided by UIView in draw(_:)). iOS developers most often work with a context obtained from UIGraphicsGetCurrentContext() inside the draw(_:) method or from UIGraphicsBeginImageContextWithOptions for offline rendering.
The context applies all settings through a state stack — the developer can save the current state (saveGState), modify parameters, and restore the previous state (restoreGState). This is critically important when drawing complex scenes where different elements require different colors, line thicknesses, and transformations. According to Apple WWDC 2023, proper use of the state stack improves code readability and prevents visual artifacts.
CGContextSaveGState pushes a copy of the current context state onto the stack. This state includes all settings: colors, line thickness, transformation, shadows, clipping area, fonts. After modifying parameters, calling CGContextRestoreGState returns the context to the saved state, undoing all intermediate changes. The stack can hold up to 32 states on iOS.
When drawing groups of elements with different styles (e.g., multi-colored chart sectors), the state stack eliminates the need to manually remember and restore each parameter. Without saveGState/restoreGState, the developer would have to reset the color, shadow, and transformation after each element. Using the stack reduces code and prevents restoration errors.
It is important to maintain a balance of saves and restores — each saveGState must have a matching restoreGState. Breaking the balance leads to state leaks or premature restoration, causing unpredictable rendering. Xcode's static analyzer does not check CGContext stack balance, so correctness is the developer's responsibility. It is recommended to group pairs in do/finally blocks.
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 sets the fill color for subsequent operations. The color is passed as CGColorRef, which contains information about the color space and color components. CGContextSetStrokeColorWithColor works similarly for stroke color. Apple recommends caching frequently used CGColorRef values for better performance.
CGContextDrawLinearGradient draws a linear gradient between two points with specified colors. CGContextDrawRadialGradient creates a radial gradient transitioning from one circle to another. To work with gradients, a CGGradientRef object is first created containing an array of colors and stop positions. Gradients in CGContext support both RGB and Grayscale color spaces.
Filling an area with a gradient requires setting up a clipping area first. Without clip, the gradient will fill the entire context, not just the desired shape. The typical pattern: add a path to the context, call clip to restrict the area, then draw the gradient. This pattern is used in all applications that require gradient fills of complex shapes, from buttons to backgrounds.
| Function | Purpose | Color Space |
|---|---|---|
| setFillColor | Sets the fill color | Any (via CGColor) |
| setStrokeColor | Sets the stroke color | Any (via CGColor) |
| drawLinearGradient | Draws a linear gradient | RGB or Grayscale |
| drawRadialGradient | Draws a radial gradient | RGB or Grayscale |
CGContextAddPath adds a previously created CGPathRef to the context for subsequent rendering. After adding a path, it can be stroked (strokePath), filled (fillPath), or both. CGContextBeginPath starts a new path, clearing the previous one. The context supports only one active path at a time.
CGContextMoveToPoint moves the current contour point to the specified coordinates. CGContextAddLineToPoint draws a straight line from the current point to the given point. CGContextAddCurveToPoint adds a cubic Bezier curve, CGContextAddQuadCurveToPoint adds a quadratic one. CGContextClosePath closes the contour with a straight line between the last and first point.
Unlike UIBezierPath, where all these methods are called on the path object, in CGContext they are called directly on the context. This difference matters when choosing an approach: UIBezierPath is more convenient for reusing paths, CGContext is better for one-time drawing with minimal overhead. CGContext also supports dashed lines via CGContextSetLineDash, flexible line caps via CGContextSetLineCap, and joins via 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 restricts the drawing area to the current path. All subsequent drawing operations will be visible only within this area. This is the basic mechanism for masking, rounding corners, and creating custom element boundaries. The clipping area is also saved and restored via saveGState/restoreGState.
CGContextTranslateCTM shifts the origin of the context coordinate system. CGContextScaleCTM scales the X and Y axes. CGContextRotateCTM rotates the coordinate system around the current origin. Transformations accumulate — each subsequent one is applied relative to the current coordinate system. Composite transformations (translation + rotation + scaling) are used for animation and element positioning.
Combining clipping and transformations provides powerful capabilities: you can draw a complex mask through clipping, then apply a transformation to the entire content. A typical example is displaying part of an image at an angle with rounded corners. Operation order matters: first set the clipping area, then apply the transformation, then draw the content. Apple recommends minimizing CTM (Current Transformation Matrix) changes in the drawing loop.
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()
}
Let's look at a complete example of creating a gradient chart using CGContext. The chart includes three sectors of different colors with gradient fills and text labels. The code demonstrates the combination of saveGState, clip, gradients, and text rendering in a single context.
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
}
}
}
In this example, each chart sector is drawn in its own saveGState/restoreGState block. This approach guarantees that color and path settings for one sector do not affect others. The addArc method creates an arc from the start to the end angle, and closePath closes the sector to the center. The label text is placed at the midpoint angle of the sector using trigonometric functions cos and sin for position calculation.
To improve performance during frequent redrawing, it is recommended to cache angle and position calculations in a separate array. CGContext executes all operations on the CPU, so complex scenes with dozens of sectors may require optimization — for example, pre-rendering in CGLayer or using Metal for GPU acceleration. For most UI tasks with 5–10 elements, CGContext performance remains acceptable.
Frequently Asked Questions
Call UIGraphicsGetCurrentContext() inside the draw(_:) method of UIView or inside a UIGraphicsBeginImageContextWithOptions block. For CALayer layers, use the draw(in:) method of CALayerDelegate, which receives a ready-made context.
fillPath fills the interior of the current path with the color from setFillColor. strokePath draws a line along the path with the parameters from setStrokeColor, setLineWidth, and setLineCap. Both operations are performed sequentially: first fill, then stroke if needed.
Bitmap CGContext is a context created via CGBitmapContextCreate that draws into a memory buffer instead of the screen. It is used for background image generation, graphics processing without display, and creating textures for Metal or OpenGL.
Core Graphics does not provide a method to reset all settings. Use saveGState before changes and restoreGState to return to the original state. Alternatively, create a new context with the same parameters.
Before calling drawLinearGradient, set the clipping area via clip on the desired path. Without clip, the gradient fills the entire context. Order: addPath → clip → drawLinearGradient — guarantees the gradient inside the shape.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also