I have an App Group shared between my app and its widgets.
I have SwiftData's @Model, updated from the widget intent's perform() method and displayed on the app and the widget.
@MainActor
func perform() async throws -> some IntentResult {
let context = try ModelContainer(for: MyModel.self).mainContext
let myModel = try ModelContainer(for: MyModel.self).mainContext.fetch(
FetchDescriptor<MyModel>(predicate: #Predicate {
// The predicate is not the problem.
})
).first
myModel?.functionThatModifiesMyModel()
return .result()
}
Both the app and the widget SwiftUI views access to the model using Macros.
@Environment(\.modelContext) var context
@Query(sort: \.whatever) var myModel: [MyModel]
But the data is never correct in the app code (the widget's data is updated correctly using the intent).
Why doesn't the model make it to the app?
Post
Replies
Boosts
Views
Activity
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.
Any idea on how to keep interactive widgets always working?