A solution is not to rely on a Data Manager class and its associates but to use UserDefaults so that Widget's getTimeline method will be called when ContentView submits data to it. By this approach, you can update Widget's View.
import SwiftUI
import WidgetKit
struct ContentView: View {
var body: some View {
VStack {
Button {
let task0 = TaskItem(id: UUID(), name: "Dish washing", isComplete: false, dueDate: Date.now.addingTimeInterval(86400))
let task1 = TaskItem(id: UUID(), name: "Bed making", isComplete: false, dueDate: Date.now.addingTimeInterval(86400 * 2))
saveTasksToWidget(tasks: [task0, task1])
} label: {
Image(systemName: "globe")
.imageScale(.large)
}
.tint(.pink)
.buttonStyle(.borderedProminent)
}
.padding()
}
private func saveTasksToWidget(tasks: [TaskItem]) {
guard let sharedDefaults = UserDefaults(suiteName: "group.abc") else {
print("Failed to get shared UserDefaults.")
return
}
do {
let encoder = JSONEncoder()
let encodedData = try encoder.encode(tasks)
sharedDefaults.set(encodedData, forKey: "widgetTasksArray")
WidgetCenter.shared.reloadAllTimelines()
} catch {
print("Error encoding tasks: \(error)")
}
}
}
// TaskItem.swift //
import Foundation
struct TaskItem: Codable, Identifiable {
let id: UUID
let name: String
let isComplete: Bool
let dueDate: Date
static let previewTasks = [
TaskItem(id: UUID(), name: "Rose", isComplete: false, dueDate: Date.now.addingTimeInterval(86400)),
TaskItem(id: UUID(), name: "Chrisanthumum", isComplete: true, dueDate: Date.now.addingTimeInterval(86400 * 2)),
TaskItem(id: UUID(), name: "Garden Dahlia", isComplete: false, dueDate: Date.now.addingTimeInterval(86400 * 3))
]
}
// CrazyWidget.swift //
import WidgetKit
import SwiftUI
struct SimpleEntry: TimelineEntry {
let date: Date
let tasks: [TaskItem]
}
struct Provider: TimelineProvider {
let appGroupID = "group.abc"
let dataKey = "widgetTasksArray"
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), tasks: TaskItem.previewTasks)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let currentTasks = loadTasks()
let entry = SimpleEntry(date: Date(), tasks: currentTasks)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let currentTasks = loadTasks()
let entry = SimpleEntry(date: Date(), tasks: currentTasks)
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 1, to: Date())!
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
private func loadTasks() -> [TaskItem] {
var taskItems: [TaskItem] = []
if let sharedDefaults = UserDefaults(suiteName: appGroupID), let savedData = sharedDefaults.data(forKey: dataKey) {
do {
let decoder = JSONDecoder()
taskItems = try decoder.decode([TaskItem].self, from: savedData)
} catch {
print("Error decoding tasks from App Group: \(error)")
taskItems = TaskItem.previewTasks // Fallback on decoding failure
}
} else {
taskItems = TaskItem.previewTasks // Fallback on access failure
}
return taskItems
}
}
struct CrazyWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading) {
Text("Pending Tasks")
.font(.headline)
.foregroundColor(.blue)
Divider()
if entry.tasks.isEmpty {
Text("All clear! No tasks found.")
.font(.caption)
.foregroundColor(.secondary)
} else {
VStack(alignment: .leading, spacing: 4) {
ForEach(entry.tasks.prefix(3)) { task in
HStack {
Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
.foregroundColor(task.isComplete ? .green : .orange)
Text(task.name)
.font(.caption)
.strikethrough(task.isComplete)
.lineLimit(1)
}
}
}
}
}
.padding()
}
}
struct CrazyWidget: Widget {
let kind: String = "RailMe"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
CrazyWidgetEntryView(entry: entry)
}
.configurationDisplayName("RailMe GGGGG")
.description("View your current tasks saved from the main app.")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
#Preview("Small - Full Data") {
CrazyWidgetEntryView(entry: SimpleEntry(date: Date(), tasks: TaskItem.previewTasks))
}