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.

@IBDesignable Attributes

Go to Main.storyboard and select the background view. In the Attributes inspector, change the @IBInspectable values to the following:

  • Light Color: RGB(255, 255, 242)
  • Dark Color: RGB(223, 255, 247)
  • Pattern Size: 30

Storyboard with Designables properties set to appropriate colors

Experiment a little more with drawing background patterns. See if you can get a polka dot pattern as a background instead of the triangles.

And of course, you can substitute your own non-vector images as repeating patterns.

Drawing Images

In the final stretch of this tutorial, you’ll make a medal to reward users for drinking enough water. This medal will appear when the counter reaches the target of eight glasses.

Medal image to show when counter hits eight glasses of water

That’s certainly not a museum-worthy piece of art. You can improve it yourself or even take it to the next level by drawing a trophy instead.

In this particular case, you only need to draw the image once when the user drinks eight glasses of water. If the user never reaches the target, there’s no need to make a medal.

Once drawn, it also doesn’t need to be redrawn with draw(_:) and setNeedsDisplay().

Time to put the brush to the canvas. Instead of using @IBDesignable, you’ll build up the medal view using a Swift playground and copy the code into the Flo project when you’re finished.

Xcode Playground

Go to File ▸ New ▸ File ▸ Playground, select Blank Playground, and click Next. Name the playground MedalDrawing, and then click Create.

In the new playground window, replace the playground code with:

import UIKit

let size = CGSize(width: 120, height: 200)

UIGraphicsBeginImageContextWithOptions(size, false, 0.0)

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

// This code must always be at the end of the playground
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

This creates a drawing context, just as you did for the patterned image.

You always need these two last lines at the bottom of the playground so you can preview the image in the playground.

Next, click the square in the gray results column to the right of this code:

let image = UIGraphicsGetImageFromCurrentImageContext()

Clicking the square in the results column

This places a preview image under the code, which will update with every change you make to the code.

Drawing Theory

It’s often best to do a sketch to wrap your head around the order you’ll need to draw the elements. For example, look at the following “masterpiece” drawn for this tutorial:

Sketch of the drawing

As shown, you’ll draw the medal in the following order:

  1. The back ribbon (red)
  2. The medallion (gold gradient)
  3. The clasp (dark gold)
  4. The front ribbon (blue)
  5. The number 1 (dark gold)

Remember to keep the last two lines of the playground (where you extract the image from the context) at the very end and add the following drawing code to the playground before those lines.

First, set up the non-standard colors you need:

// Gold colors
let darkGoldColor = UIColor(red: 0.6, green: 0.5, blue: 0.15, alpha: 1.0)
let midGoldColor = UIColor(red: 0.86, green: 0.73, blue: 0.3, alpha: 1.0)
let lightGoldColor = UIColor(red: 1.0, green: 0.98, blue: 0.9, alpha: 1.0)

This should all look familiar by now. Notice that the colors appear in the right margin of the playground as you declare them.

Lower Ribbon

Add the drawing code for the red part of the ribbon:

// Lower ribbon
let lowerRibbonPath = UIBezierPath()
lowerRibbonPath.move(to: CGPoint(x: 0, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 40, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
lowerRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
lowerRibbonPath.close()
UIColor.red.setFill()
lowerRibbonPath.fill()

This doesn’t really do anything new, just creating a path and filling it. You should see the red path appear in the image preview section.

Clasp

Next, add the following code for drawing the clasp:

// Clasp
let claspPath = UIBezierPath(
  roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), 
  cornerRadius: 5)
claspPath.lineWidth = 5
darkGoldColor.setStroke()
claspPath.stroke()

Here, you make use of UIBezierPath(roundedRect:cornerRadius:) to create the desired curves. The clasp should draw in the image preview.

Medallion

Now, it’s time to draw the medallion. Use the following code to draw the medallion:

// Medallion
let medallionPath = UIBezierPath(
  ovalIn: CGRect(x: 8, y: 72, width: 100, height: 100))
//context.saveGState()
//medallionPath.addClip()

let colors = [
  darkGoldColor.cgColor, 
  midGoldColor.cgColor, 
  lightGoldColor.cgColor
] as CFArray
guard let gradient = CGGradient(
  colorsSpace: CGColorSpaceCreateDeviceRGB(),
  colors: colors,
  locations: [0, 0.51, 1]) 
else {
  fatalError("""
    Failed to instantiate an instance \
    of \(String(describing: CGGradient.self))
    """)
}
context.drawLinearGradient(
  gradient, 
  start: CGPoint(x: 40, y: 40), 
  end: CGPoint(x: 40, y: 162), 
  options: [])
//context.restoreGState()

Notice the commented out lines. These are here to temporarily show how the gradient is drawn:

Shows how gradient in the medallion is drawn

To put the gradient on an angle, so it goes from top-left to bottom-right, change the end x coordinate of the gradient. Alter the drawLinearGradient(_:start:end:options:) code to:

context.drawLinearGradient(
  gradient, 
  start: CGPoint(x: 40, y: 40), 
  end: CGPoint(x: 100, y: 160), 
  options: [])

The rotated gradient square, which is shown as the medallion

Now, uncomment those three lines in the medallion drawing code to create a clipping path to constrain the gradient within the medallion’s circle.

Just as you did when drawing the graph in Core Graphics Tutorial: Gradients and Contexts, you save the context’s drawing state before adding the clipping path and restore it after the gradient is drawn so the context is no longer clipped.

The medallion's circle

To draw the solid internal line of the medal, use the medallion’s circle path, but scale it before drawing. Instead of transforming the whole context, you’ll just apply the transform to one path.

Add this code after the medallion drawing code:

// Create a transform
// Scale it, and translate it right and down
var transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
transform = transform.translatedBy(x: 15, y: 30)
medallionPath.lineWidth = 2.0

// Apply the transform to the path
medallionPath.apply(transform)
medallionPath.stroke()

The medallion image with outline drawn on the circle

This scales the path down to 80 percent of its original size and then translates the path to keep it centered within the gradient view.

Upper Ribbon

Add the upper ribbon drawing code at the end of the drawing code:

// Upper ribbon
let upperRibbonPath = UIBezierPath()
upperRibbonPath.move(to: CGPoint(x: 68, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 108, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
upperRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
upperRibbonPath.close()

UIColor.blue.setFill()
upperRibbonPath.fill()

This is similar to the code you added for the lower ribbon: making a Bezier path and filling it.

The medallion image with upper right ribbon drawn

Number One

The last step is to draw the number one on the medal. Add this code at the end of the drawing code:

// Number one

// Must be NSString to be able to use draw(in:)
let numberOne = "1" as NSString
let numberOneRect = CGRect(x: 47, y: 100, width: 50, height: 50)
guard let font = UIFont(name: "Academy Engraved LET", size: 60) else {
  fatalError("""
    \(#function):\(#line) Failed to instantiate font \
    with name \"Academy Engraved LET\"
    """)
}
let numberOneAttributes = [
  NSAttributedString.Key.font: font,
  NSAttributedString.Key.foregroundColor: darkGoldColor
]
numberOne.draw(in: numberOneRect, withAttributes: numberOneAttributes)

Here, you define a NSString with text attributes and draw it into the drawing context using draw(in:withAttributes:).

The medallion with number one drawn on the circle

Looking good!

You’re getting close, but it’s looking a little two-dimensional. It would be nice to have some drop shadows.

Creating Shadows

To create a shadow, you need three elements: color, offset (distance and direction of the shadow) and blur.

At the top of the playground, after defining the gold colors but just before the // Lower ribbon line, insert this shadow code:

// Add shadow
let shadow = UIColor.black.withAlphaComponent(0.80)
let shadowOffset = CGSize(width: 2.0, height: 2.0)
let shadowBlurRadius: CGFloat = 5

context.setShadow(
  offset: shadowOffset, 
  blur: shadowBlurRadius, 
  color: shadow.cgColor)

That makes a shadow, but the result is probably not what you pictured. Why is that?

The medal image with a shadow drawn for each element

When you draw an object into the context, this code creates a shadow for each object.

Three circles, each with a shadow

Ah-ha! Your medal comprises five objects. No wonder it looks a little fuzzy.

Fortunately, it’s pretty easy to fix. Simply group drawing objects with a transparency layer, and you’ll draw only one shadow for the whole group.

Three circles grouped together and a drop shadow for the whole object

Add the code to make the group after the shadow code. Start with this:

context.beginTransparencyLayer(auxiliaryInfo: nil)

When you begin a group, you also need to end it. Add this next block at the end of the playground but before the point where you retrieve the final image:

context.endTransparencyLayer()

Now, you have a completed medal image with clean, tidy shadows:

The medal image with a drop shadow for the whole medal

That completes the playground code, and you have a medal to show for it!

Adding the Medal Image to an Image View

Now that you’ve got the code in place to draw a medal (which looks fabulous, by the way), you’ll need to render it into a UIImageView in the main Flo project.

Switch back to the Flo project and create a new file for the image view.

Click File ▸ New ▸ File… and choose the Cocoa Touch Class template. Click Next, and name the class MedalView. Make it a subclass of UIImageView, then click Next. Finally, click Create.

Go to Main.storyboard and add a UIImageView as a subview of Counter View. Select the UIImageView and change the class to MedalView in the Identity inspector.

MedalView class selected in the Identity inspector

In the Size inspector, give the image view the coordinates X=76, Y=147, Width=80, and Height=80:

Frame values set in the Size inspector

In the Attributes inspector, make sure the Content Mode is set to Aspect Fit so the image automatically resizes to fit the view.

Content Mode set in the Attributes inspector

MedalView Setup

Go to MedalView.swift and add a method to create the medal:

func createMedalImage() -> UIImage {
  debugPrint("creating Medal Image")
}

This creates a log so you know when the image is being created.

Switch back to your MedalDrawing playground and copy the entire code except for the initial import UIKit.

Go back to MedalView.swift and paste the playground code into createMedalImage().

At the end of createMedalImage(), replace the last two lines with the following:

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

return image

That should squash the compile error. Notice that because UIGraphicsGetImageFromCurrentImageContext() returns an optional UIImage, you unwrap it before returning the image.

At the top of the class, add a property to hold the medal image:

lazy var medalImage = createMedalImage()

The lazy declaration modifier means that the medal image code, which is computationally intensive, only runs when necessary. Hence, if the user never records drinking eight glasses, the medal drawing code never runs.

Add a method to show the medal:

func showMedal(show: Bool) {
  image = (show == true) ? medalImage : nil
}

Go to ViewController.swift and add an outlet at the top of the class:

@IBOutlet weak var medalView: MedalView!

Next, go to Main.storyboard and connect the new MedalView to this outlet.

Then, go back to ViewController.swift and add this method to the class:

func checkTotal() {
  if counterView.counter >= 8 {
    medalView.showMedal(show: true)
  } else {
    medalView.showMedal(show: false)
  }
}

This shows the medal if you drink enough water for the day.

Call this method at the end of both viewDidLoad() and pushButtonPressed(_:):

checkTotal()

Build and run the app. Tap the plus button several times to get to eight glasses of water. Congratulations! You got yourself a medal.

Final results rendered

In the debug console, you’ll see that the creating Medal Image log only outputs when the counter reaches eight and displays the medal, because medalImage uses a lazy declaration.

Animating

Now, you can add some icing to the cake. You’ll add some simple animation to the buttons, making them spin when the user taps them. This should give you a feel of what else you can do with UIKit graphics and stimulate your curiosity.

First, add the following method to ViewController.swift:

func rotateButton(_ button: UIButton) {
  let layer = button.layer  // 1
  let rotationAnimation = 
    CAKeyframeAnimation(keyPath: "transform.rotation")  // 2
  rotationAnimation.keyTimes = [0, 1]  // 3
  rotationAnimation.values = [0, CGFloat.pi]  // 4
  rotationAnimation.duration = 0.25  // 5
  layer.add(rotationAnimation, forKey: "transform.rotation")  // 6
}

Here’s what this does:

  1. Define which layer you’ll animate.
  2. Create an instance of CAKeyframeAnimation. This animation will animate the layer by changing the value of transform.rotation.
  3. Each value in the array is a floating point number between 0.0 and 1.0 that defines when to apply the corresponding keyframe value. This property allows to create and control really complex animations. In this case, you just need the starting point 0 and the end point 1.
  4. This property contains values for each of the keyTimes. For starting point, you set rotation angle to 0 and for the end point to pi, which is 180°.
  5. Here, you specify the animation duration in seconds.
  6. Last, you add this animation to the layer, which will start the animation execution.

With this in place, you just need to call this method when user taps the button. Add this code to the end of pushButtonPressed(_:):

rotateButton(button)

Build and run your app. Tap the plus and minus buttons and enjoy your fancy new animation.

App screen with buttons spinning when tapped

Where to Go From Here?

You’ve come a long way in this epic Core Graphics tutorial. You’ve mastered the basics of Core Graphics: drawing paths, creating patterns and gradients, and transforming the context. To top it all off, you learned how to put it all together in a useful app.

Download the completed version of the Flo project using the Download Materials button.

If you’re still curious about what Core Graphics can offer, please have a look at our Drawing in iOS with Core Animation and Core Graphics video course.

I hope you enjoyed making Flo and that you’re now able to make some stunning UIs using nothing but Core Graphics and UIKit! If you have any questions or comments or want to hash out how to draw a trophy instead of a medal, please join the forum discussion below.