Using TimelineView and Canvas in SwiftUI

Learn how to use TimelineView and Canvas in SwiftUI and combine them to produce animated graphics. By Bill Morefield.

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.

Adding a Center Button

Next, you'll next add a small circle at the center of the clock face where the hands meet. After the code to draw the second hand, add:

let buttonDiameter = clockSize * 0.05
let buttonOffset = buttonDiameter / 2.0
let buttonRect = CGRect(
  x: -buttonOffset,
  y: -buttonOffset,
  width: buttonDiameter,
  height: buttonDiameter)
cgContext.addEllipse(in: buttonRect)
cgContext.fillPath()

You calculate the diameter of five percent of the clock face. Then, you shift the corner of the rectangle half of that diameter toward the upper left. Next, you add an ellipse defined by that rectangle and fill it.

Run the app to see the updated clock face:

App displaying info for Cairo with full clock with center button

One more thing, as Steve Jobs would say! You'll add a display showing the day of the month in the selected city to see how to integrate SwiftUI views with a canvas.

Mixing SwiftUI Views Into a Canvas

The canvas isn't a ViewBuilder, meaning you can't include SwiftUI views directly. You saw this when the rectangle views from DaytimeGraphicsView.swift didn't show until you converted them to canvas method calls. Instead, you can pass SwiftUI views to the canvas and reference them when drawing.

Open AnalogClock.swift, and starting on the line with the closing brace for Canvas, add the following code:

symbols: {
  ClockDayView(time: time, location: location)
    .tag(0)
}

You pass in SwiftUI views using the symbols parameter of the canvas view initializer. You must tag each view with a unique identifier using tag(_:).

Next, you need to use that tag to reference the view. At the top of the canvas closure, add the following code:

let dayView = gContext.resolveSymbol(id: 0)

This code looks for a SwiftUI view tagged with the passed identifier. If one exists, it's stored in dayView. If not, then the method returns nil.

At the end of the view, after the end of withCGContext(content:), add the following code to show the SwiftUI view on the clock face:

if let dayView = dayView {
  gContext.draw(
    dayView,
    at: CGPoint(x: clockCenter * 1.6, y: clockCenter))
}

You attempt to unwrap dayView. If successful, you use the GraphicsContext's draw(_:at:) method to draw the view on the canvas using the symbol. Note that even though you've left the closure where you changed the origin, it remains at its new position at the clock center. Hence, you use the clockCenter you calculated earlier and shift the horizontal position to the right. Run the app to see the final clock face:

App showing details for Shibuya with full clock face with date badge

Where to Go From Here?

You can download the finished project by clicking Download Materials at the top or bottom of this tutorial.

You just created an app that not only syncs times but also shows the time on an analog clock you built from scratch — great job!

For more background on rotations and the trigonometry used to draw the clock face, see Trigonometry for Game Programming — SpriteKit and Swift Tutorial: Part 1/2 and Trigonometry for Game Programming — SpriteKit and Swift Tutorial: Part 2/2.

The Beginning Core Graphics video course is another great resource. Plus, raywenderlich.com has many more Core Graphics tutorials. Here are a few of them:

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below.