I have a widget that shows me core data stuff data I modify in the app. All I want the widget to do is to refresh whenever the core data database in the main app refreshes.
Here is my widget
//
// RemindMeWidget.swift
// RemindMeWidget
//
// Created by Charel Felten on 05/07/2021.
//
import WidgetKit
import SwiftUI
import Intents
import CoreData
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(
date: Date(),
configuration: ConfigurationIntent(),
notes: Note.previewNotes,
realFamily: context.family
)
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(
date: Date(),
configuration: configuration,
notes: Note.previewNotes,
realFamily: context.family
)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
print("get timeline is called")
var notes: [Note] = []
let containerURL = PersistenceController.containerURL
let storeURL = containerURL.appendingPathComponent(PersistenceController.SQLiteStoreAppendix)
let description = NSPersistentStoreDescription(url: storeURL)
let container = NSPersistentCloudKitContainer(name: PersistenceController.containerName)
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
let viewContext = PersistenceController.shared.container.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Note")
do {
let result = try viewContext.fetch(request)
// is there a nicer way to do it?
if let tmp = result as? [Note] {
notes = tmp
}
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
notes = notes.sorted { (lhs, rhs) in
if lhs.int16priority == rhs.int16priority {
return lhs.timestamp! > rhs.timestamp!
}
return lhs.int16priority > rhs.int16priority
}
let entry = SimpleEntry(
date: Date(),
configuration: configuration,
notes: notes,
realFamily: context.family
)
print("hey")
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
var notes: [Note]
var realFamily: WidgetFamily
}
struct RemindMeWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
WidgetView(notes: entry.notes, realFamily: entry.realFamily)
}
}
@main
struct RemindMeWidget: Widget {
let kind: String = "RemindMeWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
RemindMeWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct RemindMeWidget_Previews: PreviewProvider {
static var previews: some View {
RemindMeWidgetEntryView(
entry: SimpleEntry(
date: Date(),
configuration: ConfigurationIntent(),
notes: Note.previewNotes,
realFamily: .systemSmall
)
).previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
In the app, I have a settings page where I call a manual widget refresh (for debugging for now) like this:
Button("Reload Widget") {
print("calling widgetcenter")
WidgetCenter.shared.reloadAllTimelines()
WidgetCenter.shared.getCurrentConfigurations({result in print(result)})
}
Full code:
//
// SettingsView.swift
// RemindMe
//
// Created by Charel Felten on 02/07/2021.
//
import SwiftUI
import WidgetKit
struct SettingsView: View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var config: Config
var body: some View {
NavigationView {
Form {
ForEach(Priority.allRegularCases) { priority in
PriorityView(config: config, priority: priority)
}
Section(header: Text("Theme")) {
Picker(selection: $config.colorTheme, label: Text("Theme")) {
ForEach(ColorTheme.allCases, id: \.self) { value in
Text(value.rawValue).tag(value)
}
}
}
Section(header: Text("Additional Info"), footer: Text("Toggle which additional information to show below each note in the list.")) {
Toggle(isOn: $config.showNotificationTime) {
Text("Show reminders")
}
Toggle(isOn: $config.showCreationTime) {
Text("Show date")
}
Button("Reload Widget") {
print("calling widgetcenter")
WidgetCenter.shared.reloadAllTimelines()
WidgetCenter.shared.getCurrentConfigurations({result in print(result)})
}
}
}
.navigationTitle("Settings")
}
// save the settings if we leave the app (goes away from foreground)
.onReceive(
NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification),
perform: { _ in config.save() }
)
.onDisappear(perform: Note.updateAllNotes)
}
}
struct PriorityView: View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var config: Config
var priority: Priority
var body: some View {
Section(header: Text(priority.getDescription()).foregroundColor(Color.secondary)) {
Picker(selection: $config.priorityIntervals[priority.getIndex()], label: Text("Notification interval")) {
ForEach(Interval.allCases, id: \.self) { value in
Text(value.rawValue).tag(value)
}
}
switch config.priorityIntervals[priority.getIndex()] {
case .ten_minutes, .never:
EmptyView()
default:
DatePicker("Reminders at", selection: $config.priorityDates[priority.getIndex()], displayedComponents: [.hourAndMinute])
}
}
.foregroundColor(Colors.getColor(for: priority, in: .primary))
.listRowBackground(
ZStack {
colorScheme == .dark ? Color.black : Color.white
Colors.getColor(for: priority, in: .background)
}
)
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView(
config: Config()
)
}
}
Clicking this button prints the following to my console:
calling widgetcenter
success([WidgetInfo:
- configuration: <ConfigurationIntent: 0x282e51dd0> {
}
- family: systemSmall
- kind: RemindMeWidget, WidgetInfo:
- configuration: <ConfigurationIntent: 0x282e51e60> {
}
- family: systemLarge
- kind: RemindMeWidget, WidgetInfo:
- configuration: <ConfigurationIntent: 0x282e51ef0> {
}
- family: systemMedium
- kind: RemindMeWidget])
The widget however is not refreshed. It keeps looking like this:
Sometimes I did refresh, but then when I made changes in the app, the widget did not update. None of the print statements that I put in getTimeline
are being printed. I am really exhausted and not sure what I did wrong...
I am not sure how to supply a smaller reproducible example, but I just have not idea whatsoever where the issue could come from.
I have read multiple related questions but none of them have helped:
- reloadAllTimelines not working with UserDefaults
- SwiftUI widget not refreshing
- How to refresh Widget when Main App is used?
If you want to take a look at the code, here it is:
https://github.com/charelF/RemindMeApp/tree/main/RemindMeWidget
Update
It's also not working in the simulator.
I pushed all my changes to GitHub, see https://github.com/charelF/RemindMeApp