I just experienced this problem, and thought it might help someone else out later.
User adds a widget to their home screen. The widget has dynamic options, so they edit the widget and select a value. In my case, these are events, so the user selects their "Paris Trip". The widget shows the Paris Trip data and all is well.
The user then edits the event in the main app and renames it to "Paris Vacation".
The widget on the home screen clears because the identifier that that widget was created for (i.e. the name of that event) no longer exists.
Here's my fix...
The default settings for a dynamic selection intent's Type is to have two properties: identifier and displayString. The settings for these two are greyed-out (because they shouldn't be changed).
But, you can add a new property into there, so I added a unique identifier for the event, "uniqueId".
Now, here's a gotcha. This doesn't show up anywhere until the supporting files for the intent are recreated in the background. The easiest way I found of forcing this was to quit and restart Xcode.
Here's my provideEventOptionsCollection() func in IntentHandler.swift:
As you can see, you can access the uniqueId property of the event object and add it to the event you're returning.
In my IntentTimelineProvider the event func is now this:
and the eventFromUniqueId() func in EventDetail:
Hope this helps someone out there.
User adds a widget to their home screen. The widget has dynamic options, so they edit the widget and select a value. In my case, these are events, so the user selects their "Paris Trip". The widget shows the Paris Trip data and all is well.
The user then edits the event in the main app and renames it to "Paris Vacation".
The widget on the home screen clears because the identifier that that widget was created for (i.e. the name of that event) no longer exists.
Here's my fix...
The default settings for a dynamic selection intent's Type is to have two properties: identifier and displayString. The settings for these two are greyed-out (because they shouldn't be changed).
But, you can add a new property into there, so I added a unique identifier for the event, "uniqueId".
Now, here's a gotcha. This doesn't show up anywhere until the supporting files for the intent are recreated in the background. The easiest way I found of forcing this was to quit and restart Xcode.
Here's my provideEventOptionsCollection() func in IntentHandler.swift:
Code Block swift func provideEventOptionsCollection(for intent: DynamicEventSelectionIntent, with completion: @escaping (INObjectCollection<Event>?, Error?) -> Void) { let events: [Event] = EventDetail.availableEvents.map { event in let returnEvent = Event( identifier: event.name, display: event.name ) returnEvent.uniqueId = event.uniqueId. /* <-- THIS IS THE UPDATE */ return returnEvent } let collection = INObjectCollection(items: events) completion(collection, nil) }
As you can see, you can access the uniqueId property of the event object and add it to the event you're returning.
In my IntentTimelineProvider the event func is now this:
Code Block swift if let uniqueId = configuration.event?.uniqueId { if let event = EventDetail.eventFromUniqueId(uniqueId: uniqueId) { return event } }
and the eventFromUniqueId() func in EventDetail:
Code Block swift static func eventFromUniqueId(uniqueId: String) -> EventDetail? { return (availableEvents).first(where: { (event) -> Bool in return event.uniqueId == uniqueId }) }
Hope this helps someone out there.
My widget was based on the EmojiRangerWidget sample code, which contained this:
My event() function is just based on that.
You don't need it; it's just the way I get the event for my case. Since I get the event in two places it saves duplicating the code.
In getSnapshot() and getTimeline():
That passes the intent configuration to the event() function, and returns the first event where its uniqueId matches the one provided by the intent. So, the user still selects the event's name when they edit the widget (as that's way more user-friendly), but what we're actually basing the widget on is the uniqueId instead. Does that make sense?
Code Block swift func character(for configuration: DynamicCharacterSelectionIntent) -> CharacterDetail { if let name = configuration.hero?.identifier, let character = CharacterDetail.characterFromName(name: name) { /* Save the last selected character to our App Group. */ CharacterDetail.setLastSelectedCharacter(heroName: name) return character } return .panda }
My event() function is just based on that.
You don't need it; it's just the way I get the event for my case. Since I get the event in two places it saves duplicating the code.
In getSnapshot() and getTimeline():
Code Block swift let selectedEvent = event(for: configuration)
That passes the intent configuration to the event() function, and returns the first event where its uniqueId matches the one provided by the intent. So, the user still selects the event's name when they edit the widget (as that's way more user-friendly), but what we're actually basing the widget on is the uniqueId instead. Does that make sense?