Core Graphics Tutorial: Lines, Rectangles, and Gradients

Learn how to use Core Graphics to draw lines, rectangles, and gradients — starting by beautifying a table view! By Ron Kliffer.

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

Fixing the Theme

Open StarshipsViewController.swift. At the end of viewDidLoad(), add the following:

tableView.separatorStyle = .none
tableView.backgroundColor = .starwarsSpaceBlue

Then, in tableView(_:cellForRowAt:), just before returning the cell, set the color of the text:

cell.textLabel?.textColor = .starwarsStarshipGrey

This removes the cell separators and gives the table some nice starship colors.

Next, open AppDelegate.swift and, in application(_:didFinishLaunchingWithOptions:), add the following just before returning:

// Theming
let barAppearance = UINavigationBarAppearance()
barAppearance.configureWithOpaqueBackground()
barAppearance.backgroundColor = .starwarsSpaceBlue
barAppearance.titleTextAttributes = [
  .foregroundColor: UIColor.starwarsStarshipGrey
]

UINavigationBar.appearance().tintColor = .starwarsYellow
UINavigationBar.appearance().barStyle = .black
UINavigationBar.appearance().standardAppearance = barAppearance
UINavigationBar.appearance().scrollEdgeAppearance = barAppearance

This sets the navigation bar’s appearance to match the table’s, using the UINavigationBarAppearance class introduced in iOS 13.

Build and run the app.

Less ugly cell gradient

That’s better! Your starship’s table view is starting to look space age. :]

Stroking Paths

Stroking in Core Graphics means drawing a line along a path, rather than filling it, as you did before.

When Core Graphics strokes a path, it draws the stroke line on the middle of the exact edge of the path. This can cause a couple of common problems.

Outside the Bounds

First, if you’re drawing around the edge of a rectangle — a border, for example — Core Graphics won’t draw half the stroke path by default.

Why? Because the context set up for a UIView extends only to the bounds of the view. Imagine stroking with a one-point border around the edge of a view. Because Core Graphics strokes down the middle of the path, the line will be half a point outside the bounds of the view and half a point inside the bounds of the view.

A common solution is to inset the path for the stroke rect half the width of the line in each direction, so it sits inside the view.

The diagram below shows a yellow rectangle with a red stroke one point wide on a gray background, striped at one-point intervals. In the left diagram, the stroke path follows the bounds of the view and has been cropped. You can see this because the red line is half the width of the gray squares. On the right diagram, the stroke path has been inset half a point and now has the correct line width.

Stroking on the bounds and inset 1/2 point

Anti-Aliasing

Second, you need to be aware of anti-aliasing effects that can affect the appearance of your border. Anti-aliasing is a technique rendering engines use to avoid the appearance of “jagged” edges and lines when displayed graphics don’t map perfectly to physical pixels on a device.

Take the example of a one-point border around a view from the previous paragraph. If the border follows the bounds of the view, then Core Graphics will attempt to draw a line half a point wide on either side of the rectangle.

On a non-Retina display, one point is equal to one pixel on the device. It’s not possible to light up just half of a pixel, so Core Graphics will use anti-aliasing to draw in both pixels, but in a lighter shade to give the appearance of only a single pixel.

In the following sets of screenshots, the left image is a non-Retina display, the middle image is a Retina display with a scale of two and the third image is a Retina display with a scale of three.

For the first diagram, notice how the 2x image doesn’t show any anti-aliasing, as the half point on either side of the yellow rectangle falls on a pixel boundary. However, in the 1x and 3x images, anti-aliasing occurs.

Stroking with different screen scales

In this next set of screenshots, the stroke rect has been inset half a point. Thus, the stroke line aligns exactly with point, and thus pixel, boundaries. Notice how there are no aliasing artifacts.

Stroking with different screen scales after aligning on a pixel boundary

Adding a Border

Back to your app! The cells are starting to look good, but you’re going to add another touch to make them stand out. This time, you’ll draw a bright yellow frame around the edges of the cell.

You already know how to easily fill rectangles. Well, stroking around them is just as easy.

Open StarshipListCellBackground.swift and add the following to the bottom of draw(_:):

let strokeRect = bounds.insetBy(dx: 4.5, dy: 4.5)
context.setStrokeColor(UIColor.starwarsYellow.cgColor)
context.setLineWidth(1)
context.stroke(strokeRect)

Here, you create a rectangle for stroking that’s inset from the background rectangle by 4.5 points in both the x and y directions. Then, you set the stroke color to yellow, the line width to one point and, finally, stroke the rectangle. Build and run your project.

Far far away (bordered cells)

Now, your starship list looks like it comes from a galaxy far, far away!

Building a Card Layout

Although StarshipsViewController is looking fancy, StarshipDetailViewController still needs some sprucing up!

Detail view, starter vs. finished

For this view, you’ll start by drawing a gradient on the table view background, using a custom UITableView subclass.

Create a new Cocoa Touch Class file, make it a subclass of UITableView and call it StarshipTableView. Add the following to the new class:

override func draw(_ rect: CGRect) {
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }

  context.drawLinearGradient(
    in: bounds, 
    startingWith: UIColor.starwarsSpaceBlue.cgColor, 
    finishingWith: UIColor.black.cgColor)
}

This should look familiar by now. In the draw(_:) method of your new table view subclass, you get the current CGContext, then draw a gradient in the bounds of the view, starting from blue at the top and heading into black at the bottom. Simple!

Open Main.storyboard and click the TableView in the Starship Detail View Controller scene. In the Identity inspector, set the class to your new StarshipTableView.

Using starship table view

Build and run the app, then tap the Y-wing row.

Detail view gradient background

Your detail view now has a nice full-screen gradient running from top to bottom, but the cells in the table view obscure the best parts of the effect. It’s time to fix this and add a bit more flair to the detail cells.

Open StarshipDetailViewController.swift and, at the bottom of tableView(_:cellForRowAt:), just before returning the cell for item field, add the following:

cell.textLabel?.textColor = .starwarsStarshipGrey
cell.detailTextLabel?.textColor = .starwarsYellow
cell.backgroundColor = .clear

This simply sets the cell’s field name and value to more appropriate colors for your Stars Wars theme and sets the background color to clear.

Then, after tableView(_:cellForRowAt:), add the following method to style the table view header:

override func tableView(
  _ tableView: UITableView, 
  willDisplayHeaderView view: UIView, 
  forSection section: Int
) {
  view.tintColor = .starwarsYellow
  if let header = view as? UITableViewHeaderFooterView {
    header.textLabel?.textColor = .starwarsSpaceBlue
  }
}

Here, you set the tint color of the table views’ header’s view to the theme yellow, giving it a yellow background, and its text color to the theme blue.