Core Graphics Tutorial: Patterns and Playgrounds

Learn how to draw a repeatable pattern and use Playgrounds to prototype drawing a complex image. By Andrew Kharchyshyn.

Leave a rating/review
Download materials
Save for later
Share
Update note: Andrew Kharchyshyn updated this tutorial for iOS 14, Swift 5 and Xcode 12. Caroline Begbie wrote the original.

In this tutorial, you’ll learn how to draw a repeating pattern on a view’s background, how to use Swift Playgrounds to quickly prototype your Core Graphics drawings, how to get the best out of your drawings, and how to add simple animations.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you’ll work on a water drinking tracking app, called Flo. You might remember this app from Core Graphics Tutorial: Getting Started or Core Graphics Tutorial: Gradients and Contexts tutorials. If you missed them, this is a good time to read them first, but you can also start right here.

You’re going to take Flo to its final form. Specifically, you’ll:

  • Create a repeating pattern for the background
  • Draw a medal from start to finish to award users for successfully drinking eight glasses of water a day
  • Add a simple keypath animation to the button

Repeating Pattern for Background

Your mission in this section is to use the pattern methods in UIKit to create this background pattern:

iPhone simulator with background pattern of triangles with blue and yellow colors

Note: If you need to optimize for speed, then work through Core Graphics Tutorial: Patterns, which demonstrates a basic way to create patterns using Swift and Core Graphics. For most purposes, such as when the background is drawn only once, it should be acceptable to use UIKit wrapper methods.

Setting up BackgroundView

Go to File ▸ New ▸ File…, select the iOS ▸ Source ▸ Cocoa Touch Class template and choose Next. Enter BackgroundView for the Class field and UIView for Subclass of. Make sure Swift is selected for the language. Click Next, and then Create.

Go to Main.storyboard, select the main view of ViewController and change the class to BackgroundView in the Identity inspector.

Storyboard with background view selected. Class is set to BackgroundView.

Replace the code in BackgroundView.swift with:

import UIKit

@IBDesignable
class BackgroundView: UIView {
  // 1
  @IBInspectable var lightColor: UIColor = .orange
  @IBInspectable var darkColor: UIColor = .yellow
  @IBInspectable var patternSize: CGFloat = 200

  override func draw(_ rect: CGRect) {
    // 2
    guard let context = UIGraphicsGetCurrentContext() else {
      fatalError("\(#function):\(#line) Failed to get current context.")
    }

    // 3
    context.setFillColor(darkColor.cgColor)

    // 4
    context.fill(rect)
  }
}

Then, open Main.storyboard and notice the background view of your storyboard is yellow now. Here’s why:

  1. lightColor, darkColor and patternSize have @IBInspectable attributes, so it’s easier to configure their values in the Interface Builder later. You’re using orange and yellow as temporary colors, just so you can see what’s happening. patternSize controls the size of the repeating pattern. It’s initially set to large, again so it’s easy to see what’s happening.
  2. UIGraphicsGetCurrentContext() gives you the view’s context and is also where draw(_:) draws.
  3. Use the Core Graphics setFillColor() to set the current fill color of the context. Notice that you need to use CGColor, a property of darkColor when using Core Graphics.
  4. Instead of setting up a rectangular path, fill() takes up the entire context with the current fill color.

Drawing Triangles

You’re now going to draw these three orange triangles using UIBezierPath. The numbers correspond to the points in the following code:

Layout showing how the triangles are drawn. The image includes numbers that correspond to numbers in the comments in the code below.

Still in BackgroundView.swift, add this code to the end of draw(_:):

let drawSize = CGSize(width: patternSize, height: patternSize)
    
// Insert code here

let trianglePath = UIBezierPath()
// 1
trianglePath.move(to: CGPoint(x: drawSize.width / 2, y: 0))
// 2
trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height / 2))
// 3
trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height / 2))

// 4
trianglePath.move(to: CGPoint(x: 0, y: drawSize.height / 2))
// 5
trianglePath.addLine(to: CGPoint(x: drawSize.width / 2, y: drawSize.height))
// 6
trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height))

// 7
trianglePath.move(to: CGPoint(x: drawSize.width, y: drawSize.height / 2))
// 8
trianglePath.addLine(to: CGPoint(x: drawSize.width / 2, y: drawSize.height))

// 9
trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height))

lightColor.setFill()
trianglePath.fill()

Notice how you use one path to draw three triangles. move(to:) is just like lifting your pen from the paper and moving it to a new spot when you’re drawing.

Open Main.storyboard and you’ll see an orange and yellow image at the top left of your background view.

Storyboard with view controller and yellow background. Orange triangles displayed on left top corner.

So far, you’ve drawn directly into the view’s drawing context. To repeat this pattern, you must create an image outside the context and then use that image as a pattern in the context.

Find the following line in BackgroundView.swift. It’s close to the top of draw(_:), but after the initial context call:

let drawSize = CGSize(width: patternSize, height: patternSize)

Add the following code where it conveniently says // Insert code here:

UIGraphicsBeginImageContextWithOptions(drawSize, true, 0.0)

guard let drawingContext = UIGraphicsGetCurrentContext() else {
  fatalError("\(#function):\(#line) Failed to get current context.")
}

// Set the fill color for the new context
darkColor.setFill()
drawingContext
  .fill(CGRect(x: 0, y: 0, width: drawSize.width, height: drawSize.height))

Hey! Those orange triangles disappeared from the storyboard. Where did they go?

UIGraphicsBeginImageContextWithOptions(_:_:_:) creates a new context and sets it as the current drawing context, so you’re now drawing into this new context. The parameters of this method are:

  • The size of the context.
  • Whether the context is opaque. If you need transparency, then this needs to be false.
  • The scale of the context. If you’re drawing to a Retina screen on iPhone 11, this should be 2.0, and if to an iPhone 12, it should be 3.0. Here, you use 0.0 instead, which ensures the correct scale for the device is automatically applied.

Then, you used UIGraphicsGetCurrentContext() to get a reference to this new context.

Next, you filled the new context with yellow. You could have let the original background show through by setting the context opacity to false, but it’s faster to draw opaque contexts.

Getting an Image From a Context

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

guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
  fatalError("""
    \(#function):\(#line) Failed to \
    get an image from current context.
    """)
}
UIGraphicsEndImageContext()

This extracts a UIImage from the current context. When you end the current context with UIGraphicsEndImageContext(), the drawing context reverts to the view’s context. So any further drawing in draw(_:) happens in the view.

To draw the image as a repeated pattern, add this code to the end of draw(_:):

UIColor(patternImage: image).setFill()
context.fill(rect)

This creates a new UIColor by using an image as a color instead of a solid color.

Build and run the app. You should now have a rather bright background for your app.

Background is made of yellow and orange triangles.