Core Graphics Tutorial: Gradients and Contexts

In this Core Graphics tutorial, learn how to develop a modern iOS app with advanced Core Graphics features like gradients and transformations. By Fabrizio Brancati.

Leave a rating/review
Download materials
Save for later
Share
Update note: Fabrizio Brancati updated this tutorial for iOS 13, Swift 5, and Xcode 11. Caroline Begbie wrote the original and Andrew Kharchyshyn made a previous update.

Welcome back to our modern Core Graphics tutorial series!

In Core Graphics Tutorial: Getting Started, you learned about drawing lines and arcs with Core Graphics and using Xcode’s interactive storyboard features.

In this second part, you’ll delve further into Core Graphics, learning about drawing gradients and manipulating CGContexts with transformations.

Core Graphics

You’re now going to leave the comfortable world of UIKit and enter the underworld of Core Graphics.

This image from Apple describes the relevant frameworks conceptually:

diagram showing how the app and its frameworks comprise layers

UIKit is the top layer, and it’s also the most approachable. You’ve used UIBezierPath, which is a UIKit wrapper of the Core Graphics CGPath.

The Core Graphics framework is based on the Quartz advanced drawing engine. It provides low-level, lightweight 2D rendering. You can use this framework to handle path-based drawing, transformations, color management and much more.

One thing to know about lower layer Core Graphics objects and functions is that they always have the prefix CG, so they are easy to recognize.

Getting Started

By the time you get to the end of this tutorial, you’ll have created a graph view that looks like this:

graph showing the number of glasses of water consumed over a week

Before drawing on the graph view, you’ll set it up in the storyboard and create the code that animates the transition to show it.

The complete view hierarchy will look like this:

final view hierarchy

First, download the project materials by clicking the Download Materials button at the top or bottom of this tutorial. When you open it, you’ll see that it’s pretty much where you left off in the previous tutorial. The only difference is that in Main.storyboard, CounterView is inside of another view with a yellow background. Build and run, and this is what you’ll see:

starting view showing counter with plus and minus buttons

Creating the Graph

Go to File ▸ New ▸ File…, choose the iOS ▸ Source ▸ Cocoa Touch Class template and click Next. Enter the name GraphView as the class name, choose the subclass UIView and set the language to Swift. Click Next then Create.

Now in Main.storyboard click the name of the yellow view in the Document Outline and press Enter to rename it. Call it Container View. Drag a new UIView from the object library inside of Container View, below the Counter View.

Change the class of the new view to GraphView in the Identity inspector. The only thing left is to add constraints for the new GraphView, similar to how you added constraints in the previous part of the tutorial:

  • With the GraphView selected, Control-drag from the center slightly left, still within the view, and choose Width from the pop-up menu.
  • With the GraphView still selected, Control-drag from the center slightly up, still within the view, and choose Height from the pop-up menu.
  • Control-drag left from inside the view to outside the view and choose Center Horizontally in Container.
  • Control-drag up from inside the view to outside the view, and choose Center Vertically in Container.

Edit the constraint constants in the Size inspector to match these:

Size inspector showing desired constraints

Your Document Outline should look like this:

Document Outline showing how Graph View should fit in

The reason you need a Container View is to make an animated transition between the Counter View and the Graph View.

Go to ViewController.swift and add property outlets for the Container and Graph views:

@IBOutlet weak var containerView: UIView!
@IBOutlet weak var graphView: GraphView!

This creates an outlet for the Container and Graph views. Now hook them up to the views you created in the storyboard.

Go back to Main.storyboard and hook up the Graph View and the Container View to their corresponding outlets:

Connecting ContainerView and GraphView outlets

Setting Up the Animated Transition

While still in Main.storyboard, drag a Tap Gesture Recognizer from the Object Library to the Container View in the Document Outline:

adding a tap gesture recognizer

Next, go to ViewController.swift and add this property to the top of the class:

 
var isGraphViewShowing = false

This simply marks whether the Graph View is currently displayed.

Now add this tap method to do the transition:

 
@IBAction func counterViewTap(_ gesture: UITapGestureRecognizer?) {
  // Hide Graph
  if isGraphViewShowing {
    UIView.transition(
      from: graphView,
      to: counterView,
      duration: 1.0,
      options: [.transitionFlipFromLeft, .showHideTransitionViews],
      completion: nil
    )
  } else {
    // Show Graph
    UIView.transition(
      from: counterView,
      to: graphView,
      duration: 1.0,
      options: [.transitionFlipFromRight, .showHideTransitionViews],
      completion: nil
    )
  }
  isGraphViewShowing.toggle()
}

UIView.transition(from:to:duration:options:completion:) performs a horizontal flip transition. Other available transitions are cross dissolve, vertical flip and curl up or down. The transition uses .showHideTransitionViews so that you don’t have to remove the view to prevent it from being shown once it is “hidden” in the transition.

Add this code at the end of pushButtonPressed(_:):

 
if isGraphViewShowing {
  counterViewTap(nil)
}

If the user presses the plus button while the graph is showing, the display will swing back to show the counter.

Now, to get this transition working, go back to Main.storyboard and hook up your tap gesture to the newly added counterViewTap(gesture:):

connecting the tap gesture recognizer

Build and run. Currently, you’ll see the Graph View when you start the app. Later on, you’ll set the Graph View hidden, so the counter view will appear first. Tap it and you’ll see the flip transition.

static picture of flip transition in progress

Analyzing the Graph View

annotated GraphView to illustrate the following analysis

Remember the Painter’s Model from Part 1? It explained that you draw an image from back to front in Core Graphics. So you need the order in mind before you code. For Flo’s graph, that would be:

  1. Gradient background view
  2. Clipped gradient under the graph
  3. Graph line
  4. Circles for the graph points
  5. Horizontal graph lines
  6. Graph labels

Drawing a Gradient

You’ll now draw a gradient in the Graph View.

Open GraphView.swift and replace the code with:

import UIKit

@IBDesignable
class GraphView: UIView {
  // 1
  @IBInspectable var startColor: UIColor = .red
  @IBInspectable var endColor: UIColor = .green

  override func draw(_ rect: CGRect) {
    // 2
    guard let context = UIGraphicsGetCurrentContext() else {
      return
    }
    let colors = [startColor.cgColor, endColor.cgColor]
    
    // 3
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    
    // 4
    let colorLocations: [CGFloat] = [0.0, 1.0]
    
    // 5
    guard let gradient = CGGradient(
      colorsSpace: colorSpace,
      colors: colors as CFArray,
      locations: colorLocations
    ) else {
      return
    }
    
    // 6
    let startPoint = CGPoint.zero
    let endPoint = CGPoint(x: 0, y: bounds.height)
    context.drawLinearGradient(
      gradient,
      start: startPoint,
      end: endPoint,
      options: []
    )
  }
}

Here’s what you need to know from the code above:

  1. You need to set the start and end colors for the gradient as @IBInspectable properties so that you’ll be able to change them in the storyboard.
  2. CG drawing functions need to know the context in which they will draw, so you use the UIKit method UIGraphicsGetCurrentContext() to obtain the current context. That’s the one that draw(_:) draws into.
  3. All contexts have a color space. This could be CMYK or grayscale, but here you’re using the RGB color space.
  4. The color stops describe where the colors in the gradient change over. In this example, you only have two colors, red going to green, but you could have an array of three stops, and have red going to blue going to green. The stops are between 0 and 1, where 0.33 is a third of the way through the gradient.
  5. You then need to create the actual gradient, defining the color space, colors and color stops.
  6. Finally, you need to draw the gradient. drawLinearGradient(_:start:end:options:) takes the following parameters:
    • The CGGradient with color space, colors and stops
    • The start point
    • The end point
    • Option flags to extend the gradient

The gradient will fill the entire rect passed to draw(_:).

Open Main.storyboard and you’ll see the gradient appear on the Graph View.

initial red to green gradient showing in the storyboard

In the storyboard, select the Graph View. Then in the Attributes inspector, change Start Color to RGB(250, 233, 222), and End Color to RGB(252, 79, 8). To do this, click the color, then Custom:

gradient modified to use custom colors

Now for some clean up work. In Main.storyboard, select each view in turn, except for the main view, and set the Background Color to Clear Color. You don’t need the yellow color any more and the push button views should have a transparent background anyway.

Build and run, and you’ll notice the graph looks a lot nicer, or at least its background does. :]

app with a clean graph background view