Home iOS & Swift Tutorials

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.

4.6 / 5 26 Ratings


  • Swift 5, iOS 13, Xcode 11
Update note: Andrew Kharchyshyn updated this tutorial for iOS 13, Swift 5 and Xcode 11. Caroline Begbie wrote the original.

You just finished an app. It works fine, but the interface lacks style and grace. You could give it a makeover by drawing several sizes of custom control images in Photoshop and hope Apple doesn’t release a @4x retina screen. That plan, however, lacks strategy and sounds time-consuming. Alternatively, you could use Core Graphics to create an image that scales crisply for any device size.

Core Graphics is Apple’s vector drawing framework. It’s a big, powerful application programming interface (API) with many tools to master and cool features like @IBDesignable and @IBInspectable.

But never fear! This three-part series takes a modern approach to Core Graphics. It starts slow and eases you in with fun, engaging exercises. By the end, you’ll be able to create stunning graphics for your apps.

So sit back and relax with your favorite beverage. It’s time to learn Core Graphics!

Getting Into the Flo

Imagine a doctor recommends you drink eight glasses of water a day. No problem, you think. But after a few days, you realize how easy it is to lose track. Did you down three glasses this afternoon or two? Did you have your water bottle at your desk yesterday or the day before?

In this tutorial, you’ll create an app to track your drinking habits. With it, every time you polish off a refreshing glass of H2O, you tap a counter button. As data accumulate, the app will create a graph displaying your weekly consumption.

This app will be named Flo, and here it is in its completed glory:

completed Flo app

Note: This tutorial is the first in a three-part series. In part one, you’ll create three controls using UIKit’s drawing methods. In part two, you’ll dive deeper into Core Graphics contexts and draw the graph. In part three, you’ll create the patterned background, finish Flo and award yourself a homemade Core Graphics medal. :]

Getting Started

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

Build and run. You’ll see the following:

empty screen

You now have a starter project with a storyboard and a view controller, the rest is for you to build!

Creating a Custom Drawing on Views

There are three steps for custom drawings:

  1. Create a UIView subclass.
  2. Override draw(_:) and add some Core Graphics drawing code.
  3. Take pride in your work. :]

You’ll try this out by designing a custom plus button. It will look like this:

custom-drawn plus button

Create a new file by selecting FileNewFile…. Then choose iOSSourceCocoa Touch Class. Click Next.

On this screen, name the new class PushButton, make it a subclass of UIButton, and ensure the language is Swift. Click Next and then Create.

Note: Because UIButton is a subclass of UIView, all of the methods in UIView, such as draw(_:), will be available in UIButton.

In Main.storyboard, drag a UIButton into the view controller’s view, and select the button in Document Outline.

In the Identity inspector, change the class to use your own PushButton.

Identity Inspector Push Button

Setting Auto Layout Constraints

Next you’ll set up the Auto Layout constraints:

  1. With the button selected, Control-drag from the center of the button slightly left while staying within the button. Choose Width from the pop-up menu.
  2. With the button selected, Control-drag from the center of the button slightly up while staying within the button. Choose Height from the pop-up menu.
  3. Control-drag left from inside the button to outside the button. Choose Center Vertically in Container.
  4. Control-drag up from inside the button to outside the button. Choose Center Horizontally in Container.

This creates the four required Auto Layout constraints. You can now see them in the Size inspector:

Auto Layout Constraints

Click Edit on Align center Y to, and set its constant to 100. This change shifts the vertical position of the button from the center to 100 points below the center. Similarly, change Width and Height constants to 100. The final constraints should look like this:

The constraints inspector showing width and height constraints with a constant of 100, a center Y constraint with a constant of 100, and a center X constraint.

In the Attributes inspector, remove the default title Button.

remove default title from attributes inspector

You could build and run the app right now, but if you did, you’d see a blank screen. Time to fix that!

Drawing the Button

Recall the button you’re trying to make is circular:

Add Button Final

To draw a shape in Core Graphics, you define a path that tells Core Graphics the line to trace — such as two straight lines for the plus — or the line to fill — such as the circle. If you’re familiar with Illustrator or the vector shapes in Photoshop, then you’ll understand paths.

There are three fundamentals to know about paths:

  • A path can be stroked and filled.
  • A stroke outlines the path in the current stroke color.
  • A fill will fill a closed path with the current fill color.

An easy way to create a Core Graphics path is a handy class called UIBezierPath. This class lets you develop paths with a user-friendly API. The paths can be based on lines, curves, rectangles or a series of connected points.

Start by using UIBezierPath to create a path and then filling it with a green color. Open PushButton.swift and add this method:

override func draw(_ rect: CGRect) {
  let path = UIBezierPath(ovalIn: rect)

You created an oval-shaped UIBezierPath the size of the rectangle passed to it. In this case, it’ll be the size of the 100×100 button you defined in the storyboard, so the oval will be a circle.

Since paths don’t draw anything, you can define them without an available drawing context. To draw the path, you set a fill color on the current context and then fill the path. You’ll learn more about this later.

Build and run. You’ll see the green circle.

screen with green circle

So far, you’ve discovered how to make custom-shaped views. You did this by creating a UIButton, overriding draw(_:) and adding UIButton to your storyboard.

Peeking Behind the Core Graphics Curtain

Each UIView has a graphics context. All drawing for the view renders into this context before being transferred to the device’s hardware.

iOS updates the context by calling draw(_:) whenever the view needs to be updated. This happens when:

  • The view is new to the screen.
  • Other views on top of it move.
  • The view’s hidden property changes.
  • You explicitly call setNeedsDisplay() or setNeedsDisplayInRect() on the view.
Note: Any drawing done in draw(_:) goes into the view’s graphics context. Be aware that if you draw outside of draw(_:), you’ll have to create your own graphics context.

You haven’t used Core Graphics yet in this tutorial, because UIKit has wrappers around many of the Core Graphics functions. A UIBezierPath, for example, is a wrapper for a CGMutablePath, which is the lower-level Core Graphics API.

Note: Never call draw(_:) directly. If your view is not being updated, then call setNeedsDisplay().

setNeedsDisplay() does not itself call draw(_:), but it flags the view as “dirty,” triggering a redraw using draw(_:) on the next screen update cycle. Even if you call setNeedsDisplay() five times in the same method, you’ll call draw(_:) only once.

Introducing @IBDesignable

Creating code to draw a path and then running the app to see what it looks like is as exciting as an introductory course on 1920’s tax code. Thankfully, you’ve got options. 

Live Rendering allows views to draw themselves more accurately in a storyboard by running draw(_:). What’s more, the storyboard will immediately update to changes in draw(_:). All you need is a single attribute!

Add the following just before the class declaration while in PushButton.swift:


This enables Live Rendering. Return to Main.storyboard. You’ll see that your button is a green circle, just like when you build and run.

Next, you’ll set up your screen to have the storyboard and code side-by-side.

Do this by selecting PushButton.swift to show the code. Then, Option-click Main.storyboard in Project navigator. You will see the two files side-by-side:

two files showing PushButton.swift and Main.storyboard

Close the document outline at the left of the storyboard. Do this either by dragging the edge of the document outline pane or clicking the button at the bottom of the storyboard:

close document outline

When you’re done, your screen will look like this:

two files showing PushButton.swift and Main.storyboard

In draw(_:), locate the following code: 


Then change that code to:


In the storyboard, you’ll see fill color change from green to blue. Pretty cool!

button now has blue fill

Time to create the lines for that plus sign.

Drawing Into the Context

Core Graphics uses what’s called a “painter’s model”. When you draw into a context, it’s almost like making a painting. You lay down a path and fill it, and then lay down another path on top and fill it. You can’t change the pixels that have been laid down, but you can paint over them.

This image from Apple’s documentation shows how this works. Just like painting on a canvas, the order in which you draw is critical.

add plus sign

Your plus sign will go on top of the blue circle, so you must code the blue circle first and then the plus sign. Sure, you could draw two rectangles for the plus sign, but it’s easier to draw a path and then stroke it with the desired thickness.

Add this struct and these constants inside of PushButton:

private struct Constants {
  static let plusLineWidth: CGFloat = 3.0
  static let plusButtonScale: CGFloat = 0.6
  static let halfPointShift: CGFloat = 0.5
private var halfWidth: CGFloat {
  return bounds.width / 2
private var halfHeight: CGFloat {
  return bounds.height / 2

Next, add this code at the end of draw(_:) to draw the horizontal dash of the plus sign:

//set up the width and height variables
//for the horizontal stroke
let plusWidth = min(bounds.width, bounds.height) 
  * Constants.plusButtonScale
let halfPlusWidth = plusWidth / 2

//create the path
let plusPath = UIBezierPath()

//set the path's line width to the height of the stroke
plusPath.lineWidth = Constants.plusLineWidth

//move the initial point of the path
//to the start of the horizontal stroke
plusPath.move(to: CGPoint(
  x: halfWidth - halfPlusWidth,
  y: halfHeight))

//add a point to the path at the end of the stroke
plusPath.addLine(to: CGPoint(
  x: halfWidth + halfPlusWidth,
  y: halfHeight))

//set the stroke color

//draw the stroke

In this block, you set up a UIBezierPath. You gave it a start position on the left side of the circle. You drew to the end position at the right side of the circle. Then you stroked the path outlined in white.

In your storyboard, you now have a blue circle sporting a dash in the middle:

Blue circle with dash

Note: Remember that a path simply consists of points. An easy way to grasp the concept is to imagine you have a pen in hand. You place two dots on a page. You then place the pen at the starting point and draw a line to the next point. 

That’s essentially what you did with the above code by using move(to:) and addLine(to:).

Run the application on either an iPad 2 or an iPhone 8 Plus simulator, and you’ll notice the dash is not as crisp as it should be. It has a pale blue line encircling it.

Dash is pixeled

What’s up with that?

Analyzing Points and Pixels

Back in the days of the first iPhones, points and pixels occupied the same space and were the same size. This made them basically the same thing. When retina iPhones came around, they sported four times the pixels on screen for the same number of points. 

Similarly, the iPhone 8 Plus has again increased the number of pixels for the same points.

Note: The following is conceptual; actual hardware pixels may differ. For example, after rendering 3x, the iPhone 8 Plus downsamples to display the full image on the screen. To learn more about iPhone downsampling, check out this great post.

Below is a grid of 12×12 pixels with points shown in gray and white. The iPad 2 is a direct mapping of points to pixels, so 1x. The iPhone 8 is a 2x retina screen with 4 pixels to a point. Finally, the iPhone 8 Plus is a 3x retina screen with 9 pixels to a point.

pixel comparisons

The line you just drew is 3 points high. Lines stroke from the center of the path, so 1.5 points will draw on either side of the centerline of the path.

This picture shows drawing a 3-point line on each of the devices. You can see that devices with 1x and 2x resolutions resulted in the line being drawn across half a pixel — which, of course, can’t be done. So, iOS anti-aliases the half-filled pixels with a color halfway between the two colors. The resulting line looks fuzzy.

One Pixel Line Demonstrated

In reality, retina devices with 3x resolution have so many pixels you probably won’t notice the fuzziness. But if you’re developing for non-retina screens like the iPad 2, you should do what you can to avoid anti-aliasing.

If you have oddly sized straight lines, you need to position them at plus or minus 0.5 points to prevent anti-aliasing. If you look at the diagrams above, you’ll see that a half-point on 1x screen will move the line up half a pixel. Half a point will manage a whole pixel on 2x and one-and-a-half pixels on 3x.

In draw(_:), replace move(to:) and addLine(to:) with:

//move the initial point of the path
//to the start of the horizontal stroke
plusPath.move(to: CGPoint(
  x: halfWidth - halfPlusWidth + Constants.halfPointShift,
  y: halfHeight + Constants.halfPointShift))
//add a point to the path at the end of the stroke
plusPath.addLine(to: CGPoint(
  x: halfWidth + halfPlusWidth + Constants.halfPointShift,
  y: halfHeight + Constants.halfPointShift))

Because you’re now shifting the path by half a point, iOS will now render the lines sharply on all three resolutions.

Note: For pixel perfect lines, you can draw and fill a UIBezierPath(rect:) instead of a line. Then use the view’s contentScaleFactor to calculate the width and height of the rectangle. Unlike strokes that draw outwards from the center of the path, fills only draw inside the path.

Add the vertical stroke of the plus after the previous two lines of code, but before setting the stroke color in draw(_:).

//Vertical Line
plusPath.move(to: CGPoint(
  x: halfWidth + Constants.halfPointShift,
  y: halfHeight - halfPlusWidth + Constants.halfPointShift))
plusPath.addLine(to: CGPoint(
  x: halfWidth + Constants.halfPointShift,
  y: halfHeight + halfPlusWidth + Constants.halfPointShift))

As you can see, it is almost the same code you used to draw the horizontal line on your button.

You should now see the live rendering of the plus button in your storyboard. This completes the drawing for the plus button.

plus button

Introducing @IBInspectable

There may come a moment when you tap a button more than necessary to ensure it registers. As the app developer, you’ll need to provide a way to reverse such overzealous tapping. You need a minus button.

Your minus button will be identical to your plus button, except it will forgo the vertical bar and sport a different color. You’ll use the same PushButton for the minus button. You’ll declare what sort of button it is and its color when you add it to your storyboard.  

@IBInspectable is an attribute you can add to a property that makes it readable by Interface Builder. This means that you will be able to configure the color for the button in your storyboard instead of in code.

At the top of PushButton, add these two properties:

@IBInspectable var fillColor: UIColor = .green
@IBInspectable var isAddButton: Bool = true
Note: With @IBInspectable, you must explicitly specify the type of your properties. Otherwise, you may face an issue with Xcode struggling to resolve the type.

Locate the fill color code at the top of draw(_:). It looks like this:


Change it to this:


The button will turn green in your storyboard view.

Surround the vertical line code in draw(_:) with this if statement:

//Vertical Line

if isAddButton {
  //vertical line code move(to:) and addLine(to:)
//existing code
//set the stroke color

This makes it so you only draw the vertical line if isAddButton is set. This way, the button can be either a plus or a minus button.

In your storyboard, select the push button view. The two properties you declared with @IBInspectable appear at the top of the Attributes inspector:

plus button properties in attributes inspector

Turn Is Add Button to Off. Then change the color by going to Fill ColorCustom…Color SlidersRGB Sliders. Enter the values in each input field next to the colors, RGB(87, 218, 213). It looks like this:

change fill color and is add button properties

The changes will take place in your storyboard:

minus button

Pretty cool. Now change Is Add Button back to On to return the button to a plus button.

Adding a Second Button

Add a new Button to the storyboard and select it, placing it under your existing button.

Identity Inspector Push Button

Change its class to PushButton as you did it with previous one. You’ll see a green plus button under your old plus button.

remove default title

In the Attributes inspector, change Fill Color to RGB(238, 77, 77), change Is Add Button to Off, and remove the default title Button.

Add the Auto Layout constraints for the new view. It’s similar to what you did before:

  • With the button selected, Control-drag from the center of the button slightly to the left while staying within the button. Then choose Width from the pop-up menu.
  • With the button selected, Control-drag from the center of the button slightly up while staying within the button. Choose Height from the pop-up menu.
  • Control-drag left from inside the button to outside the button. Choose Center Horizontally in Container.
  • Control-drag up from the bottom button to the top button. Choose Vertical Spacing.

After you add the constraints, edit their constant values in the Size inspector to match these:

change values in size inspector

Build and run.

screen with Plus and Minus buttons

You now have a reusable customizable view that you can add to any app. It’s also crisp and sharp on any size device.

Adding Arcs with UIBezierPath

The next customized view you’ll create is this one:

counter view

This looks like a filled shape, but the arc is a fat-stroked path. The outlines are another stroked path consisting of two arcs.

Create a new file by selecting FileNewFile…. Then choose Cocoa Touch Class, and name the new class CounterView. Make it a subclass of UIView and ensure the language is Swift. Click Next, and then click Create.

Replace the code with:

import UIKit

@IBDesignable class CounterView: UIView {
  private struct Constants {
    static let numberOfGlasses = 8
    static let lineWidth: CGFloat = 5.0
    static let arcWidth: CGFloat = 76
    static var halfOfLineWidth: CGFloat {
      return lineWidth / 2
  @IBInspectable var counter: Int = 5
  @IBInspectable var outlineColor: UIColor = UIColor.blue
  @IBInspectable var counterColor: UIColor = UIColor.orange
  override func draw(_ rect: CGRect) {

Here you created a struct with constants. You’ll use them when drawing. The odd one out, numberOfGlasses, is the target number of glasses to drink per day. When this figure is reached, the counter will be at its maximum.

You also created three @IBInspectable properties that you can update in the storyboard. counter keeps track of the number of glasses consumed. It’s an @IBDesignable property because it is useful to have the ability to change it in the storyboard, especially for testing the counter view.

Go to Main.storyboard and add a UIView above the plus PushButton. Add the Auto Layout constraints for the new view. It’s similar to what you did before:

  1. With the view selected, Control-drag from the center of the button slightly left while staying within the view. Choose Width from the pop-up menu.
  2. Similarly, with the view selected, Control-drag from the center of the button slightly up while staying within the view. Choose Height from the pop-up menu.
  3. Control-drag left from inside the view to outside the view. Choose Center Horizontally in Container.
  4. Control-drag down from the view to the top button. Choose Vertical Spacing.

Edit the constraint constants in the Size inspector to look like this:

edit constants in size inspector

In the Identity inspector, change the class of the UIView to CounterView. Any drawing that you code in draw(_:) will now appear in the view. But you haven’t added any…yet!

Enjoying an Impromptu Math Lesson

We interrupt this tutorial for a brief, and hopefully un-terrifying message from mathematics. As the The Hitchhiker’s Guide to the Galaxy advises us, Don’t Panic. :]

Drawing in the context is based on this unit circle. A unit circle is a circle with a radius of 1.0. 

high school level math

The red arrow shows where your arc will start and end, drawing in a clockwise direction. You’ll draw an arc from the position 3π/4 radians — that’s the equivalent of 135º — clockwise to π/4 radians — that’s 45º.

Radians are generally used in programming instead of degrees, and thinking in radians means you won’t have to convert to degrees every time you work with circles. Heads up: You’ll need to figure out the arc length later, and radians will come into play then.

An arc’s length in a unit circle with a radius of 1.0 is the same as the angle’s measurement in radians. Looking at the diagram above, for example, the length of the arc from 0º to 90º is π/2. To calculate the length of the arc in a real situation, take the unit circle arc length and multiply it by the actual radius.

To calculate the length of the red arrow above, you would calculate the number of radians it spans: 2π – end of arrow (3π/4) + point of arrow (π/4) = 3π/2.

In degrees that would be: 360º – 135º + 45º = 270º.

And that’s the end of our impromptu math lesson!

a heart-emoji for math

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

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

  withCenter: center,
  radius: innerArcRadius,
  startAngle: outlineEndAngle,
  endAngle: startAngle,
  clockwise: false)
//4 - close the path
outlinePath.lineWidth = Constants.lineWidth

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

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.

More like this