Hi, I am building a to do list app that has a user configurable widget which should allow the user to select any of their lists and show the to do items in that particular list. I have created an Intent Definition and can retrieve all the list names from Core Data in the Edit Widget option on long press. But when I use that list name to retrieve the to dos and display them in the SwiftUI Entry View the widget crashes because data has not yet been populated in the Timeline entry.
Intent Handler:
func provideListNameOptionsCollection(for intent: SelectListIntent, with completion: @escaping (INObjectCollection<List>?, Error?) -> Void) {
let listNames: [List] = CoreDataManager.shared.fetchAllListNames().map { listName in
let name = List(identifier: listName, display: listName)
return name
}
let collection = INObjectCollection(items: listNames)
completion(collection, nil)
}
Core Data Manager:
func fetchAllListNames() -> [String] {
var listNames = [String]()
let context = PersistenceController.init().container.viewContext
let fetchRequest = NSFetchRequest<TaskLists>(entityName: "TaskLists")
if let lists = try? context.fetch(fetchRequest) {
for list in lists {
listNames.append(list.name)
}
}
return listNames
}
func fetchItemsAgainstListName(_ name: String) -> [Item]? {
let context = PersistenceController.init().container.viewContext
let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.starred, ascending: false), NSSortDescriptor(keyPath: \Item.checked, ascending: true), NSSortDescriptor(keyPath: \Item.timestamp, ascending: false)]
fetchRequest.predicate = NSPredicate(format: "list.name_ = %@", name)
let items = try? context.fetch(fetchRequest)
return items
}
Widget
struct Provider: IntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: Date(), relevance: nil, items: [Item]())
}
func getSnapshot(for configuration: SelectListIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), relevance: nil, items: [Item]())
completion(entry)
}
func getTimeline(for configuration: SelectListIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let items = list(for: configuration)
let entries = [SimpleEntry(date: Date(), relevance: nil, items: items)]
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}
func list(for configuration: SelectListIntent) -> [Item] {
if let name = configuration.listName?.identifier, let list = CoreDataManager.shared.fetchItemsAgainstListName(name) {
return list
}
return [Item]()
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let relevance: TimelineEntryRelevance?
let items: [Item]
}
struct checklist_widgetEntryView : View {
var entry: Provider.Entry
var body: some View {
ZStack {
VStack(alignment: .leading) {
ForEach(entry.items, id: \.self) { item in
Text("\(item.content ?? "")")
}
}
}
.onAppear {
print(entry.items)
}
}
}
The error is *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Item content]: unrecognized selector sent to instance 0x6000008e1950'
and occurs on the Text
view in my ForEach
The ZStack onAppear
shows that the list is entry is indeed an empty Array but if i put a print
in fetchItemsAgainstListName
it shows that items actually contains all the todos correctly. Any ideas on what I am doing wrong here?