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
EventViewname, 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_PreviewstoEventComplicationView_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, replaceColor(event.calendar.cgColor)withevent.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
eventinto a newEventView
} 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!