Skip to main content

Swift Core Graphics

Introduction

Core Graphics is a powerful framework provided by Apple that allows developers to create custom drawings, shapes, and graphics in iOS applications. It's a low-level, high-performance 2D rendering engine that enables pixel-perfect control over the visual elements in your app.

In this guide, you'll learn the fundamentals of Core Graphics in Swift, how to create various shapes and patterns, and how to implement practical drawing functionality in your iOS applications. Core Graphics is an essential skill for iOS developers who want to create visually appealing and customized user interfaces.

Core Graphics Fundamentals

The Graphics Context

The foundation of Core Graphics is the graphics context, which is essentially a canvas where all drawing takes place. Every drawing operation in Core Graphics is performed within a context.

swift
// Creating a custom view with Core Graphics drawing
class DrawingView: UIView {
override func draw(_ rect: CGRect) {
// Get the current graphics context
guard let context = UIGraphicsGetCurrentContext() else { return }

// Drawing code goes here
}
}

Basic Drawing Concepts

Coordinate System

Core Graphics uses a coordinate system where (0,0) is at the bottom-left corner, with the y-axis pointing upward. However, in UIKit, (0,0) is at the top-left corner with the y-axis pointing downward. This is an important distinction to remember!

Paths and Strokes

A path defines a shape that you want to draw. After creating a path, you can stroke it (draw its outline) or fill it (color its interior).

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Create a path
let path = CGMutablePath()
path.addRect(CGRect(x: 50, y: 50, width: 200, height: 100))

// Add the path to the context
context.addPath(path)

// Set the stroke color to blue
context.setStrokeColor(UIColor.blue.cgColor)
context.setLineWidth(5)

// Stroke the path
context.strokePath()
}

Result: This code creates a blue rectangular outline with a width of 200 points, height of 100 points, positioned 50 points from the left and top edges of the view.

Drawing Basic Shapes

Rectangles

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Draw a filled rectangle
context.setFillColor(UIColor.red.cgColor)
context.fill(CGRect(x: 20, y: 20, width: 100, height: 100))

// Draw a rectangle with both fill and stroke
context.setFillColor(UIColor.green.cgColor)
context.setStrokeColor(UIColor.black.cgColor)
context.setLineWidth(3)

let rectangle = CGRect(x: 150, y: 20, width: 100, height: 100)
context.addRect(rectangle)
context.drawPath(using: .fillStroke)
}

Result: This draws a red filled square and a green square with a black border.

Circles and Ellipses

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Draw a circle
context.setFillColor(UIColor.purple.cgColor)
context.fillEllipse(in: CGRect(x: 50, y: 50, width: 100, height: 100))

// Draw an ellipse with a stroke
context.setStrokeColor(UIColor.orange.cgColor)
context.setLineWidth(5)
context.strokeEllipse(in: CGRect(x: 200, y: 50, width: 150, height: 80))
}

Result: This creates a purple filled circle and an orange ellipse outline.

Lines and Curves

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Draw a line
context.setStrokeColor(UIColor.blue.cgColor)
context.setLineWidth(2)
context.move(to: CGPoint(x: 20, y: 20))
context.addLine(to: CGPoint(x: 300, y: 200))
context.strokePath()

// Draw a curve
context.setStrokeColor(UIColor.green.cgColor)
context.move(to: CGPoint(x: 20, y: 250))
context.addCurve(to: CGPoint(x: 300, y: 250),
control1: CGPoint(x: 100, y: 150),
control2: CGPoint(x: 220, y: 350))
context.strokePath()
}

Result: This draws a blue straight line and a green curved line (Bezier curve).

Custom Paths and Shapes

Creating Complex Paths

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Start a new path
context.beginPath()

// Move to starting point
context.move(to: CGPoint(x: 100, y: 100))

// Add line segments
context.addLine(to: CGPoint(x: 200, y: 100))
context.addLine(to: CGPoint(x: 150, y: 200))
context.addLine(to: CGPoint(x: 100, y: 100))

// Close the path
context.closePath()

// Set fill and stroke colors
context.setFillColor(UIColor.yellow.cgColor)
context.setStrokeColor(UIColor.black.cgColor)
context.setLineWidth(3)

// Fill and stroke the path
context.drawPath(using: .fillStroke)
}

Result: This creates a yellow triangle with a black border.

Drawing a Star

Let's create a more complex example - drawing a five-pointed star:

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

let center = CGPoint(x: rect.width/2, y: rect.height/2)
let radius: CGFloat = min(rect.width, rect.height) / 2 - 20
let starPoints = 5

// Calculate points of the star
var points = [CGPoint]()

for i in 0..<starPoints * 2 {
let radius = i % 2 == 0 ? radius : radius * 0.4
let angle = CGFloat(i) * .pi / CGFloat(starPoints)
let x = center.x + radius * sin(angle)
let y = center.y - radius * cos(angle)
points.append(CGPoint(x: x, y: y))
}

// Create the path
context.beginPath()
context.move(to: points[0])

for i in 1..<points.count {
context.addLine(to: points[i])
}

context.closePath()

// Fill and stroke
context.setFillColor(UIColor.yellow.cgColor)
context.setStrokeColor(UIColor.orange.cgColor)
context.setLineWidth(3)
context.drawPath(using: .fillStroke)
}

Result: This code generates a five-pointed yellow star with an orange border.

Colors, Gradients, and Patterns

Working with Colors

Core Graphics provides several ways to work with colors:

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Using RGB color
context.setFillColor(red: 0.5, green: 0.2, blue: 0.8, alpha: 1.0)
context.fill(CGRect(x: 20, y: 20, width: 100, height: 100))

// Using UIKit colors
context.setFillColor(UIColor.systemTeal.cgColor)
context.fill(CGRect(x: 140, y: 20, width: 100, height: 100))
}

Creating Gradients

Gradients are transitions between colors and can add depth to your drawings:

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

let colors = [UIColor.red.cgColor, UIColor.yellow.cgColor] as CFArray
let colorSpace = CGColorSpaceCreateDeviceRGB()

// Create gradient locations (where each color should be positioned from 0 to 1)
let locations: [CGFloat] = [0.0, 1.0]

// Create the gradient
guard let gradient = CGGradient(
colorsSpace: colorSpace,
colors: colors,
locations: locations
) else { return }

// Draw a linear gradient
let startPoint = CGPoint(x: 0, y: 0)
let endPoint = CGPoint(x: rect.width, y: rect.height)

context.drawLinearGradient(
gradient,
start: startPoint,
end: endPoint,
options: []
)
}

Result: This creates a red to yellow diagonal gradient filling the entire view.

Radial Gradient

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

let colors = [UIColor.blue.cgColor, UIColor.clear.cgColor] as CFArray
let colorSpace = CGColorSpaceCreateDeviceRGB()
let locations: [CGFloat] = [0.0, 1.0]

guard let gradient = CGGradient(
colorsSpace: colorSpace,
colors: colors,
locations: locations
) else { return }

let center = CGPoint(x: rect.width/2, y: rect.height/2)

// Draw a radial gradient
context.drawRadialGradient(
gradient,
startCenter: center,
startRadius: 10,
endCenter: center,
endRadius: 200,
options: []
)
}

Result: This creates a blue circle that fades to transparent at the edges.

Transformations and State Management

Transformations

Core Graphics allows you to apply transformations like rotation, scaling, and translation to your drawings:

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Draw a rectangle
let rectangle = CGRect(x: -50, y: -25, width: 100, height: 50)

// Save the current graphics state
context.saveGState()

// Move to the center of the view
context.translateBy(x: rect.width/2, y: rect.height/2)

// Rotate by 45 degrees
context.rotate(by: .pi/4)

// Draw the rectangle with the applied transformations
context.setFillColor(UIColor.purple.cgColor)
context.fill(rectangle)

// Restore the graphics state
context.restoreGState()

// Draw another shape without the transformation
context.setFillColor(UIColor.green.cgColor)
context.fill(CGRect(x: 50, y: 50, width: 80, height: 80))
}

Result: This draws a rotated purple rectangle in the center of the view and a green square in the top-left area.

Saving and Restoring State

The saveGState() and restoreGState() methods are crucial for managing the drawing state:

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Set initial state
context.setLineWidth(2)
context.setStrokeColor(UIColor.black.cgColor)

// Draw a line with the initial state
context.move(to: CGPoint(x: 20, y: 20))
context.addLine(to: CGPoint(x: 100, y: 20))
context.strokePath()

// Save the state
context.saveGState()

// Modify the state
context.setLineWidth(5)
context.setStrokeColor(UIColor.red.cgColor)

// Draw with the modified state
context.move(to: CGPoint(x: 20, y: 50))
context.addLine(to: CGPoint(x: 100, y: 50))
context.strokePath()

// Restore to the original state
context.restoreGState()

// Draw with the restored state
context.move(to: CGPoint(x: 20, y: 80))
context.addLine(to: CGPoint(x: 100, y: 80))
context.strokePath()
}

Result: This draws three horizontal lines - a thin black line at the top, a thick red line in the middle, and another thin black line at the bottom.

Text and Images

Drawing Text

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

let text = "Hello Core Graphics!"
let font = UIFont.systemFont(ofSize: 24)
let textAttributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: UIColor.darkBlue
]

// Calculate text size
let textSize = text.size(withAttributes: textAttributes)

// Calculate position for centered text
let textRect = CGRect(
x: (rect.width - textSize.width) / 2,
y: (rect.height - textSize.height) / 2,
width: textSize.width,
height: textSize.height
)

// Draw the text
text.draw(in: textRect, withAttributes: textAttributes)
}

Drawing Images

swift
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

// Get an image
guard let image = UIImage(named: "example-image") else { return }

// Draw the image
image.draw(in: CGRect(x: 50, y: 50, width: 200, height: 200))

// Draw image with alpha
context.saveGState()
context.setAlpha(0.5)
image.draw(in: CGRect(x: 150, y: 150, width: 200, height: 200))
context.restoreGState()
}

Real-World Example: Custom Progress Bar

Let's create a practical example of a custom progress bar using Core Graphics:

swift
class CustomProgressBar: UIView {
// Progress value from 0.0 to 1.0
var progress: CGFloat = 0.0 {
didSet {
// Ensure progress stays between 0 and 1
progress = min(1.0, max(0.0, progress))
setNeedsDisplay()
}
}

// Colors
var progressColor: UIColor = .systemBlue
var trackColor: UIColor = .systemGray5

override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

let cornerRadius: CGFloat = rect.height / 2

// Draw track (background)
let trackPath = UIBezierPath(
roundedRect: rect,
cornerRadius: cornerRadius
)

trackColor.setFill()
trackPath.fill()

// Draw progress
let progressWidth = rect.width * progress
let progressRect = CGRect(
x: 0,
y: 0,
width: progressWidth,
height: rect.height
)

if progressWidth > 0 {
let progressPath = UIBezierPath(
roundedRect: progressRect,
cornerRadius: cornerRadius
)

progressColor.setFill()
progressPath.fill()
}
}
}

// Usage:
let progressBar = CustomProgressBar(frame: CGRect(x: 50, y: 100, width: 300, height: 20))
view.addSubview(progressBar)

// Animate the progress
UIView.animate(withDuration: 2.0) {
progressBar.progress = 0.75
}

Result: This creates a rounded progress bar that animates from empty to 75% full over 2 seconds.

Real-World Example: Pie Chart

Another practical use of Core Graphics is creating data visualizations like charts:

swift
class PieChartView: UIView {
// Data for pie segments (values should sum to 1.0)
var segments: [(value: CGFloat, color: UIColor)] = [
(0.3, .systemRed),
(0.25, .systemBlue),
(0.2, .systemGreen),
(0.15, .systemOrange),
(0.1, .systemPurple)
]

override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }

let center = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height) * 0.4

// Start at the top (negative is up in CG coordinates)
var startAngle: CGFloat = -.pi / 2

// Draw each segment
for segment in segments {
let endAngle = startAngle + (segment.value * .pi * 2)

// Create path for this segment
context.move(to: center)
context.addArc(
center: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: false
)
context.closePath()

// Set fill color and fill
context.setFillColor(segment.color.cgColor)
context.fillPath()

// Update start angle for next segment
startAngle = endAngle
}
}
}

// Usage:
let pieChart = PieChartView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
view.addSubview(pieChart)

Result: This creates a pie chart with five colored segments representing the provided data values.

Summary

Core Graphics is a powerful framework that allows iOS developers to create custom drawings and graphics with precise control. In this guide, we've covered:

  • The fundamentals of Core Graphics and how to work with contexts
  • Drawing basic shapes like rectangles, circles, and lines
  • Creating complex paths and custom shapes
  • Working with colors, gradients, and patterns
  • Applying transformations and managing state
  • Drawing text and images
  • Real-world examples like progress bars and charts

Remember that Core Graphics is performance-intensive, so it's best to cache drawings when possible rather than redrawing them repeatedly.

Additional Resources

  1. Apple's Core Graphics Framework Reference
  2. Quartz 2D Programming Guide
  3. WWDC Sessions on Graphics and Animation

Exercises

  1. Create a custom loading spinner that animates using Core Graphics
  2. Build a bar chart that displays multiple data points
  3. Design a custom switch control with animated transitions
  4. Implement a drawing app that lets users draw on the screen
  5. Create a custom image filter effect using Core Graphics

By mastering Core Graphics, you'll be able to create unique, visually appealing UI components that will make your iOS applications stand out.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)