Inside a widget, there is a button,
Button(intent: AnAppIntent()) {
// Button's label.
}
// It seems this modifier does not add any value.
.invalidatableContent()
connected to an AppIntent.
struct AnAppIntent: AppIntent {
static var title: LocalizedStringResource = "An AppIntent"
init() {
// AppIntent required init.
}
func perform() async throws -> some IntentResult {
// Never called when the app is running.
return .result()
}
}
The button calls AppIntent's perform()
when tapped, and it consequently updates the widget UI (with or without the modifier .invalidatableContent()
) only when the app is closed completely.
If the app is alive in the background, perform()
is not called, and the widget UI never updates.
The user must explicitly dismiss the app to make the widget work as expected.
The problem may be in the timeline used.
struct SimpleEntry: TimelineEntry {
let date: Date
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
completion(SimpleEntry(date: Date()))
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
entries.append(SimpleEntry(date: entryDate))
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
However, if the problem were the timeline, the widget would not work with the app dismissed.
The file including the Intent needs to have a dual target membership: It needs to be part of both the widget extension target as well as the app target.
So make sure to tick those checkboxes.