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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Core Graphics Tutorial: Patterns and Playgrounds
25 mins
- Getting Started
- Repeating Pattern for Background
- Setting up BackgroundView
- Drawing Triangles
- Getting an Image From a Context
- @IBDesignable Attributes
- Drawing Images
- Xcode Playground
- Drawing Theory
- Lower Ribbon
- Clasp
- Medallion
- Upper Ribbon
- Number One
- Creating Shadows
- Adding the Medal Image to an Image View
- MedalView Setup
- Animating
- Where to Go From Here?
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:
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.
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:
-
lightColor
,darkColor
andpatternSize
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. -
UIGraphicsGetCurrentContext()
gives you the view’s context and is also wheredraw(_:)
draws. - Use the Core Graphics
setFillColor()
to set the current fill color of the context. Notice that you need to useCGColor
, a property ofdarkColor
when using Core Graphics. - 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:
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.
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.
@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
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.
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()
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:
As shown, you’ll draw the medal in the following order:
- The back ribbon (red)
- The medallion (gold gradient)
- The clasp (dark gold)
- The front ribbon (blue)
- 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:
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: [])
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.
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()
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.
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:)
.
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?
When you draw an object into the context, this code creates a shadow for each object.
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.
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:
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.
In the Size inspector, give the image view the coordinates X=76, Y=147, Width=80, and Height=80:
In the Attributes inspector, make sure the Content Mode is set to Aspect Fit so the image automatically resizes to fit the view.
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.
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:
- Define which layer you’ll animate.
- Create an instance of
CAKeyframeAnimation
. This animation will animate the layer by changing the value oftransform.rotation
. - 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 point1
. - This property contains values for each of the
keyTimes
. For starting point, you set rotation angle to0
and for the end point topi
, which is 180°. - Here, you specify the animation duration in seconds.
- 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.
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.
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more