Code Block swift@main |
struct WhensThatWidget: Widget { |
public var body: some WidgetConfiguration { |
IntentConfiguration(kind: "com.company.product.widget", intent: DynamicEventSelectionIntent.self, provider: Provider()) { entry in |
MyWidgetEntryView(entry: entry) |
} |
.supportedFamilies([.systemSmall, .systemMedium]) |
.configurationDisplayName("Title in the Preview screen") |
.description("Description under the preview title") |
} |
} |
|
struct EventEntry: TimelineEntry { |
public let date: Date |
let relevance: TimelineEntryRelevance? |
let event: EventDetail /* Like CharacterDetail */ |
} |
|
struct Provider: IntentTimelineProvider { |
typealias Intent = DynamicEventSelectionIntent /* The Event intent type */ |
public typealias Entry = EventEntry /* The struct above */ |
|
/* The placeholder is what's seen when you go to add the widget. It needs info up-front, so either preview data or an actual Event. */ |
func placeholder(in context: Context) -> EventEntry { |
EventDetail.getUpdatedEventsFromDefaults() |
let coverEventDetail: EventDetail? = getCoverEventFromAllEvents() /* This gets a specific event I want to put in the preview, rather than fake preview data. */ |
if(coverEventDetail == nil) { |
/* There is no cover event, so return preview data */ |
return EventEntry(date: Date(), relevance: nil, event: .previewData) |
|
} else { |
/* We have a cover event, so we can use that. It means that when the user goes to add the widget, the preview screen will have their actual cover event. */ |
return EventEntry(date: Date(), relevance: nil, event: coverEventDetail!) |
} |
} |
|
/* The snapshot is where the widget gets its running data from. Think of it as a timeline of data. You start with data A, then at 10am it displays data B, etc. |
My app doesn't need to change the data, so it doesn't here. The code in the EmojiRanger widget is easy enough to understand. */ |
func getSnapshot(for configuration: DynamicEventSelectionIntent, in context: Context, completion: @escaping (EventEntry) -> Void) { |
EventDetail.getUpdatedEventsFromDefaults() |
let coverEventDetail: EventDetail? = getCoverEventFromAllEvents() |
|
var entry: EventEntry |
if(coverEventDetail == nil) { /* No cover event means no events at all */ |
if(context.isPreview) { |
/* This lets you check if you're in the preview screen. If so, we can use the cover event, or the preview data. */ |
entry = EventEntry(date: Date(), relevance: nil, event: .previewData) |
|
} else { |
/* Not in a preview, and no cover event, so return an error. |
This is an EventDetail object stored in the EventDetail.swift struct. It shows a specific error if there are no events. */ |
entry = EventEntry(date: Date(), relevance: nil, event: .errorData) |
} |
|
} else { /* Otherwise, return the cover event details */ |
entry = EventEntry(date: Date(), relevance: nil, event: coverEventDetail!) |
EventDetail.setLastSelectedEvent(eventName: coverEventDetail!.name) |
} |
|
completion(entry) |
} |
|
/* This is where the widget gets its running data from. You start with data A, then at 10am it displays data B, etc. |
Once the timeline runs out of data, you can ask the main app for new data. My app doesn't need to change the data, so it doesn't here. The code in the EmojiRanger widget is easy enough to understand. */ |
func getTimeline(for configuration: DynamicEventSelectionIntent, in context: Context, completion: @escaping (Timeline<EventEntry>) -> Void) { |
EventDetail.getUpdatedEventsFromDefaults() |
|
/* Get the event from its name. This simply runs through all available events and gets the object that has this name. */ |
let selectedEvent = event(for: configuration) |
let relevance = TimelineEntryRelevance(score: 10) |
|
/* Create and return the EventEntry for the selected event */ |
EventDetail.setLastSelectedEvent(eventName: selectedEvent.name) |
let entry = EventEntry(date: selectedEvent.date, relevance: relevance, event: selectedEvent) /* Using the data we just got from above. */ |
let timeline = Timeline(entries: [entry], policy: .never) /* Don't need to change the data, so just pass an array of this one EventEntry. */ |
|
completion(timeline) |
} |
|
/* This is how we get the specific EventDetail for the name passed in from the intent. I've left simple NSLog debugging lines in in case you hit a problem. */ |
func event(for configuration: DynamicEventSelectionIntent) -> EventDetail { |
/* Gets an event object from its name, then sets this event as the last selected one. |
Returns the errorData if the event name couldn't be found. */ |
|
if let actualName = configuration.event?.identifier { |
NSLog("Event name = %@", actualName) |
if let event = EventDetail.eventFromName(eventName: actualName) { |
EventDetail.setLastSelectedEvent(eventName: actualName) |
NSLog("Found event with name = %@", actualName) |
return event |
} else { |
NSLog("Couldn't get event from event name: %@", actualName) |
} |
} else { |
NSLog("actualName not valid") |
} |
|
EventDetail.getUpdatedEventsFromDefaults() |
let coverEventDetail: EventDetail? = getCoverEventFromAllEvents() |
if(coverEventDetail == nil) { |
NSLog("Returning .errorData") |
return .errorData |
|
} else { |
NSLog("Returning coverEventDetail") |
EventDetail.setLastSelectedEvent(eventName: coverEventDetail!.name) |
return coverEventDetail! |
} |
} |
} |