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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

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:

  1. Define the center point you’ll rotate the arc around.
  2. Calculate the radius based on the maximum dimension of the view.
  3. Define the start and end angles for the arc.
  4. Create a path based on the center point, radius and angles you defined.
  5. 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.

Note: This is generally all you need to know when drawing arcs. But if you want to dive further into this topic, then Core Graphics Tutorial on Arcs and Paths will help.

Build and run. This is what you’ll see:

screen with an arc and plus and minus buttons

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:

  1. outlineEndAngle is the angle where the arc should end; it’s calculated using the current counter value.
  2. outlinePath is the outer arc. UIBezierPath() takes the radius to calculate the length of the arc as this arc is not a unit circle.
  3. 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.
  4. 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:

view counter view

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).

change counter color and outline color

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:

add constraints to label

In the Attributes inspector, change Alignment to center, font size to 36 and the default label title to 8.

change properties in attributes inspector

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 PushButtons.

view controller's IBOutlet and IBAction connected to storyboard

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.

screen with two buttons and an arc with number 3 in the middle

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:

If you have any questions or comments, please join the forum discussion below.