Core Graphics Tutorial: Getting Started
In this Core Graphics tutorial, you’ll learn about using Core Graphics to design pixel-perfect views and how to use Xcode’s interactive storyboards. 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
Contents
Core Graphics Tutorial: Getting Started
35 mins
- Getting Into the Flo
- Getting Started
- Creating a Custom Drawing on Views
- Setting Auto Layout Constraints
- Drawing the Button
- Peeking Behind the Core Graphics Curtain
- Introducing @IBDesignable
- Drawing Into the Context
- Analyzing Points and Pixels
- Introducing @IBInspectable
- Adding a Second Button
- Adding Arcs with UIBezierPath
- Enjoying an Impromptu Math Lesson
- Returning to Arcs
- Outlining the Arc
- Making it All Work
- Where to Go From Here?
Returning to Arcs
In CounterView.swift, add this code to draw(_:)
to draw the arc:
// 1
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
// 2
let radius = max(bounds.width, bounds.height)
// 3
let startAngle: CGFloat = 3 * .pi / 4
let endAngle: CGFloat = .pi / 4
// 4
let path = UIBezierPath(
arcCenter: center,
radius: radius/2 - Constants.arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// 5
path.lineWidth = Constants.arcWidth
counterColor.setStroke()
path.stroke()
Here’s what each section does:
- Define the center point you’ll rotate the arc around.
- Calculate the radius based on the maximum dimension of the view.
- Define the start and end angles for the arc.
- Create a path based on the center point, radius and angles you defined.
- Set the line width and color before finally stroking the path.
Imagine drawing this with a compass. You’d put the point of the compass in the center, open the arm to the radius you need, load it with a pen and spin it to draw your arc.
In this code, center
is the point of the compass. radius
is the width the compass is open, minus half the width of the pen. And the arc width is the width of the pen.
Build and run. This is what you’ll see:
Outlining the Arc
When you indicate you’ve enjoyed a cool glass of water, an outline on the counter will show you your progress toward the eight-glass goal. This outline will consist of two arcs, one outer and one inner, and two lines connecting them.
In CounterView.swift , add this code to the end of draw(_:)
:
//Draw the outline
//1 - first calculate the difference between the two angles
//ensuring it is positive
let angleDifference: CGFloat = 2 * .pi - startAngle + endAngle
//then calculate the arc for each single glass
let arcLengthPerGlass = angleDifference / CGFloat(Constants.numberOfGlasses)
//then multiply out by the actual glasses drunk
let outlineEndAngle = arcLengthPerGlass * CGFloat(counter) + startAngle
//2 - draw the outer arc
let outerArcRadius = bounds.width/2 - Constants.halfOfLineWidth
let outlinePath = UIBezierPath(
arcCenter: center,
radius: outerArcRadius,
startAngle: startAngle,
endAngle: outlineEndAngle,
clockwise: true)
//3 - draw the inner arc
let innerArcRadius = bounds.width/2 - Constants.arcWidth
+ Constants.halfOfLineWidth
outlinePath.addArc(
withCenter: center,
radius: innerArcRadius,
startAngle: outlineEndAngle,
endAngle: startAngle,
clockwise: false)
//4 - close the path
outlinePath.close()
outlineColor.setStroke()
outlinePath.lineWidth = Constants.lineWidth
outlinePath.stroke()
A few things to go through here:
-
outlineEndAngle
is the angle where the arc should end; it’s calculated using the currentcounter
value. -
outlinePath
is the outer arc.UIBezierPath()
takes the radius to calculate the length of the arc as this arc is not a unit circle. - Adds an inner arc to the first arc. It has the same angles but draws in reverse. That’s why clockwise was set to false. Also, this draws a line between the inner and outer arc automatically.
- Closing the path automatically draws a line at the other end of the arc.
With counter
in CounterView.swift set to 5, your CounterView
will look like this in the storyboard:
Open Main.storyboard and select CounterView. In the Attributes inspector, change Counter to check out your drawing code. You’ll find it is completely interactive. Experiment by adjusting the counter to be more than eight and less than zero.
Change Counter Color to RGB(87, 218, 213), and change Outline Color to RGB(34, 110, 100).
Making it All Work
Congrats! You have the controls. Next, you’ll wire them up so the plus button increments the counter and the minus button decrements the counter.
In Main.storyboard, drag a UILabel to the center of Counter View. Make sure it is a subview of Counter View. Add constraints to center the label vertically and horizontally. When you finish, it will have constraints that look like this:
In the Attributes inspector, change Alignment to center, font size to 36 and the default label title to 8.
Go to ViewController.swift and add these properties to the top of the class:
//Counter outlets
@IBOutlet weak var counterView: CounterView!
@IBOutlet weak var counterLabel: UILabel!
While still in ViewController.swift, add this method to the end of the class:
@IBAction func pushButtonPressed(_ button: PushButton) {
if button.isAddButton {
counterView.counter += 1
} else {
if counterView.counter > 0 {
counterView.counter -= 1
}
}
counterLabel.text = String(counterView.counter)
}
Here you increment or decrement the counter depending on the button’s isAddButton
. Though you could set the counter to fewer than zero, it probably won’t work with Flo. Nobody can drink negative water. :]
You also updated the counter value in the label.
Next, add this code to the end of viewDidLoad()
to ensure that the initial value of the counterLabel
will be updated:
counterLabel.text = String(counterView.counter)
In Main.storyboard, connect CounterView outlet and UILabel outlet. Connect the method to Touch Up Inside event of the two PushButton
s.
Build and run.
See if your buttons update the counter label. They should. But you may notice the counter view isn’t updating. Think way back to the beginning of this tutorial. Remember, you only called draw(_:)
when other views on top of it moved, its hidden property changed, the view was new to the screen or the app called the setNeedsDisplay()
or setNeedsDisplayInRect()
on the view.
However, the counter view needs to be updated whenever the counter property is updated; otherwise, it looks like the app is busted.
Go to CounterView.swift and change the declaration of counter
to:
@IBInspectable var counter: Int = 5 {
didSet {
if counter <= Constants.numberOfGlasses {
//the view needs to be refreshed
setNeedsDisplay()
}
}
}
This code refreshes the view only when the counter is less than or equal to the user's targeted glasses.
Build and run. Everything should now be working properly.
Where to Go From Here?
You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.
Amazing! You've covered basic drawing in this tutorial. You can now change the shape of views in your UIs. But wait — there's more!
In Part 2 of this series, you'll explore Core Graphics contexts in more depth and create a graph of your water consumption over time.
If you'd like to learn more about custom layouts, consider the following resources:
- Check out this page with list of resources provided by Apple.
- Follow our video course on Core Graphics if you prefer the video format.
If you have any questions or comments, please join the forum discussion below.