is there a demo app for the "Add configuration and intelligence to your widgets" WWDC session

is there a demo app for the "Add configuration and intelligence to your widgets" WWDC2020 session?

Xcode doesn't autogenerate any of the code that the demo says it will and parts of the code show in the video is cut off on the screen so I can't see what the code is. keen to get the app so I can manually copy it in.

also the written instructions at https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget
seem to miss parts out as Xcode doesn't generate the code that the instructions say it will. a demo app for that will also be helpful
I'm going to reply in a few stages, as there's a limit to what I can post...

The downloadable code is here: h t t p s : / / developer.apple.com/documentation/widgetkit/building <underscore> widgets <underscore> using <underscore> widgetkit <underscore> and <underscore> swiftui (Sorry about that, can't put a url from their own website in here...?!)

I've used it to successfully figure out how to get a widget to work, and with Intents, too. I won't show you how to create the Intents. I just opened the sample code and created the same intent definition thing within my own app. Note that you need to create a new target. Mine is called WidgetIntentHandler, and it has just an info.plist, WidgetIntentHandler.entitlements file and the IntentHandler.swift file. If I recall correctly it was generated, but I changed one line:

Code Block swift
import Intents
class IntentHandler: INExtension, DynamicEventSelectionIntentHandling {
func provideEventOptionsCollection(for intent: DynamicEventSelectionIntent, with completion: @escaping (INObjectCollection<Event>?, Error?) -> Void) {
/* let events: [Event] = EventDetail.availableEvents.map { event in */
/* I removed that line because I want the updated events from my defaults */
let events: [Event] = getAllEventsFromDefaults().map { event in
let event = Event(identifier: event.name, display: event.name)
return event
}
let collection = INObjectCollection(items: events)
completion(collection, nil)
}
override func handler(for intent: INIntent) -> Any {
return self
}
}

If it helps, here's some code and a little explanation in the comments. Note, my widget uses things called Events, like the EmojiRanger widget uses Characters. I also have one main event called the Cover Event.

A note about EventDetail.getUpdatedEventsFromDefaults(), which is called in a few places:
In the EmojiRanger widget, the CharacterDetail struct has a line: static let availableCharacters = [panda, egghead, spouty]

I don't have any default events (panda, egghead, spouty) to put in my array, so I replaced this with: static var availableEvents: [EventDetail] = getAllEventsFromDefaults()

getAllEventsFromDefaults() is in a separate MyWidgetUtils.swift file. It gets an NSDictionary from my defaults, converts each record into an EventDetail object, and returns an array of them, so my availableEvents array has the right data from the start. You might have it grab data from an API.

I then have a func in EventDetail.swift to re-populate the array. This is called in the main widget code, as you'll see if you read on, i.e. EventDetail.getUpdatedEventsFromDefaults():
Code Block swift
static func getUpdatedEventsFromDefaults() {
availableEvents = getAllEventsFromDefaults()
}

MyWidget.swift:
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!
}
}
}

Still in MyWidget.swift:
Code Block swift
struct MyWidgetEntryView: View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
@ViewBuilder
var body: some View {
if(entry.event.category == "ERROR") { /* Here, I'm checking if the data the widget has been passed is the errorData. If so, I know that it has a category value of "ERROR" so I'm able to show an error view instead of the actual widget. This is simply a different View object entirely with specific Text objects. */
switch family {
case .systemSmall: EventNotSelectedView()
case .systemMedium: EventNotSelectedView()
default: EventNotSelectedView()
}
} else {
switch family {
/* Just return the actual widget. */
case .systemSmall: SmallWidgetView(event: entry.event)
case .systemMedium: MediumWidgetView(event: entry.event)
default: EventNotSelectedView()
}
}
}
}
struct SmallWidgetView: View {
var event: EventDetail
var body: some View {
/* Your actual widget view for small */
}
}
struct MediumWidgetView: View {
var event: EventDetail
var body: some View {
/* Your actual widget view for medium */
}
}
struct EventNotSelectedView: View {
var body: some View {
/* My error view */
}
}
struct PlaceholderView : View {
var body: some View {
MyWidgetEntryView(entry: EventEntry(date: Date(), relevance: nil, event: .previewData))/* .redacted(reason: .placeholder) */
/* You can show the widget without any actual data, and a bunch of redacted rounded rectangles if you uncomment that bit on the line above. */
}
}
/* Shows previews in the SwiftUI of the types above */
struct MyWidget_Previews: PreviewProvider {
static var previews: some View {
Group {
PlaceholderView().previewContext(WidgetPreviewContext(family: .systemSmall))
MyWidgetEntryView(entry: EventEntry(date: Date(), relevance: nil, event: .previewData)).previewContext(WidgetPreviewContext(family: .systemSmall))
MyWidgetEntryView(entry: EventEntry(date: Date(), relevance: nil, event: .previewData)).previewContext(WidgetPreviewContext(family: .systemMedium))
EventNotSelectedView().previewContext(WidgetPreviewContext(family: .systemSmall))
EventNotSelectedView().previewContext(WidgetPreviewContext(family: .systemMedium))
}
}
}

Hope this helps!
  1. Hopefully, this will help you. Below is the sample widget code for IntentWidget

https://developer.apple.com/documentation/widgetkit/building_widgets_using_widgetkit_and_swiftui

2. Under here is the link for the [wwdc2020] demo session. I would recommend you to go through the entire course to understand the widgets completely.
https://developer.apple.com/videos/play/wwdc2020/10036

is there a demo app for the "Add configuration and intelligence to your widgets" WWDC session
 
 
Q