UIKit Apprentice, Second Edition – Now Updated!

Learn iOS and Swift from scratch. Build four powerful apps—with support for iPad and Dark Mode. Publish apps to the App Store.

Home iOS & Swift Tutorials

Core Graphics Tutorial: Patterns

Learn how to use Core Graphics to draw patterns in a performant way.

5/5 2 Ratings

Version

  • Swift 5, iOS 14, Xcode 12
Update note: Michael Katz updated this tutorial for iOS 14, Xcode 12 and Swift 5. Ray Wenderlich wrote the original.

Core Graphics is a powerful, yet friendly, set of APIs for drawing in a UIKit application. In addition to primitives like shapes and gradients, with Core Graphics you can code up patterns. Core Graphics Patterns are an arbitrary sets of graphics operations that can be tiled to fill an area. You can create magnificent backgrounds for your apps using repeated shapes. Core Graphics Patterns are a performant way to scale a drawing to fill any shape on the screen.

In this tutorial, you’ll learn how to use Core Graphics to do the following:

  • Create a path-based drawing.
  • Draw a pattern.
  • Transform the pattern.
  • Use patterns to finish a pattern recognition game called Recall.
Note: If you’re brand new to Core Graphics, it would be a good idea to review some of our entry-level tutorials on the topic. Consider working through the Lines, Rectangles, and Gradients and Arcs and Paths tutorials to get a better understanding of the foundations you’ll build upon here.

Getting Started

Start by clicking the Download Materials button at the top or bottom of this tutorial. Build and run the starter app. You’ll see this (colors and letters may vary):

Starting version of Recall

Recall takes inspiration from a game in the Left vs Right brain training app. The goal of the game is to choose the most popular direction for objects in view. A new set of objects displays once you make a choice. You have five attempts before the game ends.

Recall groups pattern objects in four quadrants. Each quadrant in the starter app has a label. The text represents the direction and the background color represents the fill color.

As a starting point, the game is rather underwhelming. :[

Raze Face Not Impressed

Your task is to use Core Graphics patterns to turn this sad app into the finished app below:

Final version of Recall

Look at the project in Xcode. These are the main files:

  • GameViewController.swift: Controls the gameplay and displays the game view.
  • ResultViewController.swift: Displays the final score and a button to restart the game.
  • PatternView.swift: Displays the pattern view for one of the quadrants in the game view.

Toward the end of this tutorial, you’ll enhance PatternView to display the desired pattern.

For starters, you’ll prototype your new and improved PatternView in a Playground. This allows you to iterate much faster while learning the ins and outs of Core Graphics patterns. Once you’re done, you’ll transfer the relevant code over to the Recall starter project.

In Xcode, go to File ▸ New ▸ Playground…. Select the Single View template. Click Next, name the playground PatternView.playground, add it to the Recall workspace and the Recall folder group and click Create. Your playground contains a view controller with a single view.

Note: If Xcode gives you an error trying to load the new program, the age-old “restart Xcode” should get you on your way.

Select Editor ▸ Run Playground to execute the playground. Click Show the Assistant Editor to display your starter view. It displays the iconic “Hello World!” result:

Hello world playground output

As you go through the next sections, you’ll replace the starter view with your pattern view. Time to get started!

Understanding the Anatomy of a Pattern

In our previous Core Graphics tutorials, you’ve seen how to define and paint paths like this:

two shape outlines, one white and one filled with yellow

A path is a set of instructions describing a shape. The example above shows you stroking the path on the left with a black line. The path on the right has been stroked with black and filled in with orange.

Using Core Graphics, you can also stroke or fill a path with a pattern. The example below shows a colored pattern filling out a path:

Drawing filled with pattern of colored squares

Setting up the Pattern

You set up a pattern by doing the following:

  1. Write a method that draws an individual pattern cell.
  2. Create a pattern with parameters that include how to draw and place an individual cell.
  3. Define the color information that your pattern will use.
  4. Paint the desired path with the pattern you created.

Now, check out a slightly different pattern cell with extra padding. The thin black border shows the bounds of the cell:

Pattern cell with blue, pink, green and red squares

You will write a draw method that draws within the bounds of the cell. Core Graphics clips anything drawn outside the cell bounds. Core Graphics also expects you to draw the pattern cell exactly the same way each time.

Your draw method can apply color when setting up your pattern cell. This is a colored pattern. An uncolored or masking pattern is one where you apply the fill color outside of the draw method. This gives you the flexibility to set up your pattern colors where it makes sense.

Core Graphics calls your draw method repeatedly to set up your pattern. The pattern creation parameters define what the pattern looks like. The example below shows a basic repeated pattern with the cells lining up right next to each other:

Repeated pattern cell

You can specify the spacing between the pattern cells when you configure the pattern:

Repeated pattern cell with offset

You can also apply transformations to change the pattern’s appearance. The pictures below show the pattern drawn inside a space represented by the fuzzy border:

three variations of the repeated pattern cell

The first shows an unchanged pattern. In the second, you see a translated pattern. The third shows the pattern rotated. Again, the black border around the pattern cells highlights its bounds.

You have a lot of options available when configuring a pattern. You’ll start putting all this together in the next section.

Creating the Pattern View

Add the following code before the view controller class in PatternView.playground:

class PatternView: UIView {
  override func draw(_ rect: CGRect) {
    // 1
    guard let context = UIGraphicsGetCurrentContext()
    else { return }
    // 2
    UIColor.orange.setFill()
    // 3
    context.fill(rect)
  }
}

This represents the custom view for your pattern. Here, you override draw(_:) to do the following:

  1. Get the view’s graphics context.
  2. Set the current fill color for the context.
  3. Fill the entire context with the current fill color.

Think of the graphics context as a canvas you can draw on. The context contains information such as the color that will fill in or stroke a path. You can draw out paths in your canvas before painting them in with a context’s color information.

Inside MyViewController, replace the code in loadView() related to label with the following:

let patternView = PatternView()
patternView.frame = CGRect(x: 10, y: 10, width: 200, height: 200)
view.addSubview(patternView)

This creates an instance of the pattern view, sets its frame and adds it to view.

Press Shift-Command-Return to run the playground. The previous label is gone and replaced with an orange subview:

Orange square in the top left corner

Coloring is just the beginning of the journey. You know there’s more where that came from!

Raze face bring it on

Drawing a Black Circle in the Pattern Cell

Add the following property inside the top of PatternView:

let drawPattern: CGPatternDrawPatternCallback = { _, context in
  context.addArc(
    center: CGPoint(x: 20, y: 20),
    radius: 10.0,
    startAngle: 0,
    endAngle: 2.0 * .pi,
    clockwise: false)
  context.setFillColor(UIColor.black.cgColor)
  context.fillPath()
}

The code above draws a circular path in the graphics context and fills it with black color.

This represents your pattern cell’s drawing method, which is of the type CGPatternDrawPatternCallback. This is a closure with two arguments:

  1. A pointer to private data associated with the pattern. You’re not using private data, so you use an unnamed parameter here.
  2. The graphics context used in drawing your pattern cell.

Add the following code to the end of draw(_:):

var callbacks = CGPatternCallbacks(
  version: 0, 
  drawPattern: drawPattern, 
  releaseInfo: nil)

CGPatternCallbacks is a structure for holding the callbacks used to draw a pattern. There are two types of callback — one for the pattern, which you’ve made already, and one for cleaning up and releasing any private data, which you aren’t using. You would typically set up a release callback if you’re using private data in the pattern. Since you don’t use private data in your draw method, you pass nil for this callback.

Creating the Pattern

To create the pattern, add the following right after the code above:

guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
  matrix: .identity,
  xStep: 50,
  yStep: 50,
  tiling: .constantSpacing,
  isColored: true,
  callbacks: &callbacks)
else { return }

This creates a pattern object. In the code above, you pass in the following parameters:

  • info: A pointer to any private data you want to use in your pattern callbacks. You pass in nil here since you’re not using any.
  • bounds: The pattern cell’s bounding box.
  • matrix: A matrix that represents the transform to apply. You pass in the identity matrix, as you’re not applying any transforms.
  • xStep: The horizontal spacing between pattern cells.
  • yStep: The vertical spacing between pattern cells.
  • tiling: The technique Core Graphics should use to account for differences between user space units and device pixels.
  • isColored: Whether the pattern cell draw method applies color. You’re setting this to true since your draw method sets a color.
  • callbacks: A pointer to the structure that holds the pattern callbacks.

Add the following code right after the pattern assignment:

var alpha: CGFloat = 1.0
context.setFillPattern(pattern, colorComponents: &alpha)
context.fill(rect)

The code above sets the fill pattern for the graphics context. For colored patterns, you must also pass in an alpha value to specify the pattern opacity. The pattern draw method provides the color. Finally, the code paints the view’s frame area with the pattern.

Run the playground by pressing Shift-Command-Return. Your pattern isn’t showing up. Strange. What’s going on?

Setting the Pattern’s Color Space

You need to provide Core Graphics with information about your pattern’s color space so it knows how to handle pattern colors. Color space specifies how to interpret a color value for display.

Add the following before the alpha declaration:

// 1
guard let patternSpace = CGColorSpace(patternBaseSpace: nil)
else { return }
// 2
context.setFillColorSpace(patternSpace)

Here’s what the code does:

  1. Create a pattern color space. The base space parameter should be nil for colored patterns. This delegates the coloring to your pattern cell draw method.
  2. Set the fill color space to your defined pattern color space.

Run the playground. Yes! You should now see a circular black pattern:

Orange rectangle filled with black circle pattern

Next, you’ll learn more about configuring patterns.

Configuring the Pattern

In your playground, change the spacing parameters that set up pattern as follows:

xStep: 30,
yStep: 30,

Run the playground. Note that the circular dots appear to be much closer to each other:

Orange rectangle filled with denser black circle pattern

This makes sense, as you’ve shrunk the step size between pattern cells.

Now, change the spacing parameters as follows:

xStep: 20,
yStep: 20,

Run the playground. Your circles have turned into quarters:

Circles in pattern shown as quarters

Changing the Centers of the Circles

To understand why, note that your draw method returns a circle with a radius of 10 centered at (20, 20). The pattern’s horizontal and vertical displacement is 20. The cell’s bounding box is 20×20 at origin (0,0). This results in a repeating quarter-circle that starts at the lower right edge.

Change drawPattern that draws the circle (context.addArc) to the following:

context.addArc(
  center: CGPoint(x: 10, y: 10), 
  radius: 10.0,
  startAngle: 0, 
  endAngle: 2.0 * .pi,
  clockwise: false)

You’ve changed the center point to be (10, 10) rather than (20, 20).

Run the playground. You’re back to whole circles due to the shift in the circle’s center:

Orange square filled with black circles touching each other

The pattern cell bounds also match up perfectly with the circle, resulting in each cell abutting the others.

Applying a Transformation to the Pattern

You can transform your pattern in many interesting ways. Inside draw(_:), replace pattern with the following:

// 1
let transform = CGAffineTransform(translationX: 5, y: 5)
// 2
guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
  matrix: transform,
  xStep: 20,
  yStep: 20,
  tiling: .constantSpacing,
  isColored: true,
  callbacks: &callbacks)
else { return }

You’ve modified the pattern by passing it a transformation matrix. Here’s a closer look:

  1. Create an affine transformation matrix that represents a translation.
  2. Configure the pattern to use this transformation by passing it in matrix.

Run the playground. Note how the pattern shifted to the right and downward to match the translation you defined:

Black circle pattern shifted right and down

Besides translating your pattern, you can also scale and rotate the pattern cells. You’ll see how to rotate the pattern later when you build the pattern for the game app.

Adding Fill and Stroke to the Pattern

Here’s how to fill and stroke a colored pattern. In drawPattern replace the lines where you set the fill color and fill the path with the following:

context.setFillColor(UIColor.yellow.cgColor)
context.setStrokeColor(UIColor.darkGray.cgColor)
context.drawPath(using: .fillStroke)

Here, you change the fill color to yellow and set the stroke color. You then call drawPath(using:) with the option that fills and strokes the path.

Run your playground and check that the pattern now shows your new fill color and stroke:

Yellow circles with black outlines on an orange background

Thus far, you’ve worked with colored patterns and defined the colors in the pattern draw method. In the finished game, you’ll have to create patterns with different colors. You probably realize that writing a draw method for each color isn’t the way to go. This is where masking patterns come into play.

Using Masking Patterns

Masking patterns define their color information outside the pattern cell draw method. This allows you to change up the pattern color to suit your needs.

Here’s an example of a masking pattern that has no color associated with it:

Gray star image as a mask pattern

With the pattern in place, you can now apply color. The first example below shows a blue color applied to the mask, and the second displays an orange color:

Star pattern in blue and orange

Now, you’ll change the pattern you’ve been working with to a masking pattern.

Delete the code from drawPattern where you set fill and stroke colors and draw the path and replace it with the following:

context.fillPath()

This reverts the code back to filling the path.

Replace pattern with the following:

guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
  matrix: transform,
  xStep: 25,
  yStep: 25,
  tiling: .constantSpacing,
  isColored: false,
  callbacks: &callbacks)
else { return }

This sets isColored to false, changing your pattern to a masking pattern. You’ve also increased the vertical and horizontal spacing to 25. Now, you need to provide the color space information for your pattern.

Replace the existing patternSpace assignment with the following:

let baseSpace = CGColorSpaceCreateDeviceRGB()
guard let patternSpace = CGColorSpace(patternBaseSpace: baseSpace)
else { return }

Here, you get a reference to a standard device-dependent RGB color space. You then change your pattern color space to this value instead of the previous nil value.

Below that, replace the lines where you create alpha and set the context fill pattern with the following:

let fillColor: [CGFloat] = [0.0, 1.0, 1.0, 1.0]
context.setFillPattern(pattern, colorComponents: fillColor)

This creates a color applied underneath the mask when filling out the pattern.

Run the playground. Your pattern’s color updates to reflect the cyan color setting that you configured outside the draw method:

Cyan circle pattern on orange background

Stroking and Filling the Masking Pattern

Now, it’s time to stroke and fill a masking pattern. It’s like stroking a colored pattern.

Replace the line context.fillPath() in drawPattern with the following:

context.setStrokeColor(UIColor.darkGray.cgColor)
context.drawPath(using: .fillStroke)

Although you set the stroke color inside draw(_:), your pattern color is still set outside the method.

Run the playground to see the stroked pattern:

Cyan circle pattern on orange background

You’ve now built up experience with different pattern configurations and with masking patterns. You can begin building out the pattern you’ll need for Recall.

Creating the Game Pattern

Add the following code to the top of the playground:

extension CGPath {
  // 1
  static func triangle(in rect: CGRect) -> CGPath {
    let path = CGMutablePath()
    // 2
    let top = CGPoint(x: rect.width / 2, y: 0)
    let bottomLeft = CGPoint(x: 0, y: rect.height)
    let bottomRight = CGPoint(x: rect.width, y: rect.height)
    // 3
    path.addLines(between: [top, bottomLeft, bottomRight])
    // 4
    path.closeSubpath()
    return path
  }
}

Going through the code, step by step:

  1. Extend CGPath to create a triangular path.
  2. Specify the three points that make up the triangle.
  3. Add lines between the points.
  4. Close the path.

Then inside PatternView, add the following empty enum:

enum Constants {
  static let patternSize: CGFloat = 30.0
  static let patternRepeatCount: CGFloat = 2
}

These represent the constants you’ll use when setting up your pattern. patternSize defines the pattern cell size, and patternRepeatCount defines the number of pattern cells in the pattern view.

Drawing a Triangle

Add the following after the Constants definition:

let drawTriangle: CGPatternDrawPatternCallback = { _, context in
  let trianglePath = CGPath.triangle(in:
    CGRect(
      x: 0,
      y: 0,
      width: Constants.patternSize,
      height: Constants.patternSize))
  context.addPath(trianglePath)
  context.fillPath()
}

This defines a new callback for drawing your triangular pattern. In it, you call CGPath.triangle(in:) to return a path representing the triangle. Then you add this path to the context before filling it.

Note that the closure doesn’t specify a fill color, so it can be a masking pattern.

In draw(_:), change callbacks to the following:

var callbacks = CGPatternCallbacks(
  version: 0, 
  drawPattern: drawTriangle, 
  releaseInfo: nil)

You’re now using the triangle drawing callback.

Drawing Repeating Triangles as a Pattern

Delete drawPattern, as it’s no longer necessary. One can only go around in circles for so long. :]

Also, in draw(_:), replace the code that assigns transform and pattern with the following:

// 1
let patternStepX = rect.width / Constants.patternRepeatCount
let patternStepY = rect.height / Constants.patternRepeatCount
// 2
let patternOffsetX = (patternStepX - Constants.patternSize) / 2.0
let patternOffsetY = (patternStepY - Constants.patternSize) / 2.0
// 3
let transform = CGAffineTransform(
  translationX: patternOffsetX,
  y: patternOffsetY)
// 4
guard let pattern = CGPattern(
  info: nil,
  bounds: CGRect(
    x: 0,
    y: 0,
    width: Constants.patternSize,
    height: Constants.patternSize),
  matrix: transform,
  xStep: patternStepX,
  yStep: patternStepY,
  tiling: .constantSpacing,
  isColored: false,
  callbacks: &callbacks)
else { return }

Here’s what that code does, step by step:

  1. Calculate the horizontal and vertical step size using the view’s width and height, as well as the number of pattern cells in a view.
  2. Work out the dimensions to horizontally and vertically center a pattern cell within its bounds.
  3. Set up a CGAffineTransform translation based on the centering variables you defined.
  4. Create the pattern object based on your calculated parameters.

Run the playground. You will see four triangles, each centered both vertically and horizontally within their bounds:

Four cyan triangles pointing upward on an orange background

You’ll next get your background colors to more closely match the Recall app.

In MyViewController, change the background color setup in loadView() as follows:

view.backgroundColor = .lightGray

Next, go to PatternView and change the context fill setup in draw(_:) as follows:

UIColor.white.setFill()

Run the playground. Your main view’s background should now be gray with a white background for your pattern view:

Four cyan triangles pointing upward on a white background

Customizing the Pattern View

Now that you have the basic pattern displaying correctly, you can make changes to control the pattern direction.

Add the following enumeration near the top of PatternView after Constants:

enum PatternDirection: CaseIterable {
  case left
  case top
  case right
  case bottom
}

This represents the different directions the triangle can point. They match the directions in your starter app.

Add the following properties to PatternView:

var fillColor: [CGFloat] = [1.0, 0.0, 0.0, 1.0]
var direction: PatternDirection = .top

This represents the color you’ll apply to the masking pattern and the pattern direction. The class sets a default color of red (the four array components represent red, green, blue and alpha) and the default direction of top.

Delete the local fillColor declaration found near the bottom of draw(_:). This will ensure that you use the instance property instead.

Replace transform with the following:

// 1
var transform: CGAffineTransform
// 2
switch direction {
case .top:
  transform = .identity
case .right:
  transform = CGAffineTransform(rotationAngle: 0.5 * .pi)
case .bottom:
  transform = CGAffineTransform(rotationAngle: .pi)
case .left:
  transform = CGAffineTransform(rotationAngle: 1.5 * .pi)
}
// 3
transform = transform.translatedBy(x: patternOffsetX, y: patternOffsetY)

Here’s what just happened:

  1. Declare a CGAffineTransform variable for your pattern transform.
  2. Assign the transform to the identity matrix if the pattern direction is top. Otherwise, the transform is a rotation based on the direction. For example, if the pattern points right, then the rotation is π / 2 radians or 90º clockwise.
  3. Apply a CGAffineTransform translation to center the pattern cell within its bounds.

Run the playground. Your triangles are red, based on your default pattern fill color:

Four red triangles pointing upward on a white background

Changing the Pattern’s Color and Direction

Now’s a great time to set up code to control and test the pattern color and direction.

Add the following methods in PatternView after the property definitions:

init(fillColor: [CGFloat], direction: PatternDirection = .top) {
  self.fillColor = fillColor
  self.direction = direction
  super.init(frame: CGRect.zero)
}
  
required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
}

This sets up an initializer that takes in a fill color and a pattern direction. direction has a default value.

You’ve also added the required initializer for when a storyboard initializes the view. You’ll need this later when you transfer your code to the app.

In MyViewController, change patternView since you’ve changed the initializer:

let patternView = PatternView(
  fillColor: [0.0, 1.0, 0.0, 1.0],
  direction: .right)

Here, you instantiate a pattern view with non-default values.

Run the playground. Your triangles are now green and pointing to the right:

Green triangles pointing to the right on a white background

Congratulations! You’re done prototyping your pattern using Playgrounds. It’s time to use this pattern in Recall.

Updating the Game to Use the Pattern

Open the Recall starter project. Go to PatternView.swift and copy over the CGPath extension from your playground to the end of the file.

Next, replace PatternView in PatternView.swift with the class from your playground.


Note
: You can vastly simplify this process by using Xcode’s code folding feature. In the playground, put the cursor just after the opening brace of class PatternView: UIView { and select Editor ▸ Code Folding ▸ Fold from the menu. Triple-click the resulting folded line to select the entire class and press Command-C. In the project, repeat the process to fold and select the class. Press Command-V to replace it.

Build and run the app. You should see something like this:

Pattern with all red triangles pointing upward

Something’s not quite right. Your pattern appears to be stuck in default mode. It looks like a new game view isn’t refreshing the pattern views.

Go to GameViewController.swift and add the following to the end of setupPatternView(_:towards:havingColor:):

patternView.setNeedsDisplay()

This prompts the system to redraw the pattern so that it picks up the new pattern information.

Build and run the app. You should now see colors and directions nicely mixed up:

Triangles of various colors pointing in different directions

Tap one of the answer buttons and play through the game to check that everything works as expected.

Congratulations on completing Recall! You’ve come a long way from the mind-numbing days of simple paint jobs.

Raze face paint ninja

Considering Performance When Using Patterns

Core Graphics patterns are really fast. Here are several options you can use to draw patterns:

  1. Using the Core Graphics pattern APIs that you worked through in this tutorial.
  2. Using UIKit wrapper methods such as UIColor(patternImage:).
  3. Drawing the desired pattern in a loop with many Core Graphics calls.

If your pattern is only drawn once, the UIKit wrapper method is the easiest. Its performance should also be comparable to the lower-level Core Graphics calls. An example of when to use this is to paint a static background pattern.

Core Graphics can work in a background thread, unlike UIKit, which runs on the main thread. Core Graphics patterns are more performant with complicated drawings or dynamic patterns.

Drawing the pattern in a loop is the slowest option. A Core Graphics pattern makes the draw call once and caches the results, making it more efficient.

Where to Go From Here?

Access the final project by clicking the Download Materials button at the top or bottom of this tutorial. You can also use this to check your work if you get stuck.

You should now have a solid understanding of how to use Core Graphics to create patterns. Check out the Patterns and Playgrounds tutorial to learn how to build patterns using UIKit.

You may also want to read through the Curves and Layers tutorial, which focuses on drawing various curves and cloning them using layers.

We hope you enjoyed this tutorial. If you have any comments or questions, please join the forum discussion below!

Average Rating

5/5

Add a rating for this content

2 ratings

More like this

Contributors

Comments