watchOS: Complications

Feb 7 2023 · Swift 5.6, watchOS 8.5, Xcode 13

Part 2: Tinted & Custom Complications

12. Refactor SwiftUI Views

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 11. Build a SwiftUI View for a Complication Next episode: 13. Display a SwiftUI View in a Complication

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 12. Refactor SwiftUI Views

Right now, the code for our event view has multiple issues. Not only are you unable to preview the complication, but everything is tied to EventKit.

What happens when you work with another calendaring platform? To solve this, we’ll start by creating an Event type in a new Swift file called Event.

Creating an Event type

We’ll want both SwiftUI and EventKit for this type, and we’ll make it struct called “Event”

import EventKit
import SwiftUI

struct Event {

}

It should hold the five properties we’re currently using in our custom event view: color, start and end dates, a title, and an optional location.

struct Event {
  let color: Color
  let startDate: Date
  let endDate: Date
  let title: String
  let location: String?

Notice how we’re asking for a Color, not a CGColor. Your use case is currently for events, which use CGColor, but you may be passing events from elsewhere in the future. And we don’t really want our view to be concerned with converting colors.

So we’re just giving the view what it wants! Now to create events, we would have to manually fill in each property in the initializer that Swift generates for our struct.

But we can make it easier to use at the call site by offering another initializer that takes in a single type, and setting our properties from there.

EKEvent is a common use case, so if we provide one to an intializer, it helps to simplify the rest of the codebase.

  init(ekEvent: EKEvent) {
    color = Color(ekEvent.calendar.cgColor)
    startDate = ekEvent.startDate
    endDate = ekEvent.endDate
    title = ekEvent.title
    location = ekEvent.location
  }

If we do that, we lose the magical memberwise initializer that a Swift structure gives us out of the box. That might OK, but we actually want to be able to manually set all of the properties for our SwiftUI previews!

So, I’ll show you a snazzy Swift trick. If we move this initializer into an extension, instead…

extension Event {
// Move init(ekevent:) here
}

We’ll get access to the auto-generated memberwise initializer again, and we can get our EventView previewing!

If your app expands in the future to include other calendar types, you would simply add a new initializer to the extension.

Refactoring the view

OK, let’s take another look at EventView.swift

Sometimes you find that you’ve named a view poorly and need to fix it. In this case, EventView should really be EventComplicationView because you need an EventView for previewing the event.

So, let’s fix that.

  • Right-click the EventView name, then choose Refactor - Rename…
  • Then add Complication between Event and View, so that the view is named EventComplicationView.
  • Press Enter you rename all instances of the view across the entire project as well as the filename.
  • You’ll still need to manually rename EventView_Previews to EventComplicationView_Previews.

Now, we’re going to move this HStack into a new EventView.

  • Put your cursor in the HStack, Hold Option-Command and click, then select “extract subview”
  • Rename the extracted view to “EventView”
  • Replace this instance of EventView with a TODO, for now. We’ll come back to it!
  • Then move the formatter property to the new EventView

At this point, Xcode isn’t happy because it doesn’t know what event means. So, add an event property

let event: Event
  • Finally, in the foregroundColor, replace Color(event.calendar.cgColor) with event.color.

And now cut this entire EventView, create a new SwiftUI View file, and paste all of that in to replace the starter code.

Your refactored view is now complete, with the exception of fixing the EventView_Previews.

Add a static property to represent a mock Event, using that magic memberwise initializer. Since this is only for a development preview, it’s OK to skip the standard calendrical calculation methods and directly add one hour to the current time.

static var event = Event(
  color: .blue,
  startDate: .now,
  endDate: .now.addingTimeInterval(3600),
  title: "Gnomes rule!",
  location: "Everywhere"
)

And pass the event into EventView

EventView(event: event)

If you reload the canvas now, you should see a preview of your event view! You want to see the view as it will appear on a complication. So, import ClockKit at the top of the file:

import ClockKit

And now, you can update the body in preview to be more helpful.

  CLKComplicationTemplateGraphicRectangularFullView(
    EventView(event: event)
  )
    .previewContext()

If it’s not already showing, bring up the canvas by pressing Option-Command-Enter. The canvas will now show what the view looks like by itself, and how it appears on the watch face!

Note: If both previews look the same for you, try closing and opening the canvas again.

Now that you know the display looks the way you want, switch back to EventComplicationView.swift. Add a new property to the view:

let event: Event?
  • Find our TODO from earlier,
  • and update the if check to just check that event property.
  • Then pass the event into a new EventView
} else if let event = event {
  EventView(event: event)

You’re simply passing the event-specific details to the view that shows them properly. In the previews below, pass nil to make the compiler happy:

EventComplicationView(event: nil)

There’s just one last thing to fix!

In CalendarComplicationApp.swift, we need to update things to compile. Just pass nil here, as well.

EventComplicationView(event: nil)

Now the app builds without errors. If you run the app right now, you’d always see a message saying there were no more events for today.

Why? You just told CalendarComplicationApp to pass nil for the event. Remember, this app is all about the complication, so you don’t really care about what displays if you run the app!