I'm trying to fetch Entities and Attributes from my Core Data to be displayed in a Widget and really struggling. Currently I can fetch the ItemCount of an Entity, but that's not what I'm trying to display. I'd like to be able to display the strings and images that I have saved in Core Data. I've looked at tons of sites, including this post, but haven't had success.
My current setup has a working App Group for the Core Data which is shared to the main app and the widget.
Here's the Widget.swift code that works to display the Entity's ItemCount:
import WidgetKit
import SwiftUI
import CoreData
private struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
let midnight = Calendar.current.startOfDay(for: Date())
let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!
let entries = [SimpleEntry(date: midnight)]
let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
completion(timeline)
}
}
private struct SimpleEntry: TimelineEntry {
let date: Date
}
private struct MyAppWidgetEntryView: View {
var entry: Provider.Entry
let moc = PersistenceController.shared.container.viewContext
let predicate = NSPredicate(format: "favorite == true")
let request = NSFetchRequest<Project>(entityName: "Project")
var body: some View {
// let result = try moc.fetch(request)
VStack {
Image("icon")
.resizable()
.aspectRatio(contentMode: .fit)
Text("Item Count: \(itemsCount)")
.padding(.bottom)
.foregroundColor(.secondary)
}
}
var itemsCount: Int {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Project")
do {
return try PersistenceController.shared.container.viewContext.count(for: request)
} catch {
print(error.localizedDescription)
return 0
}
}
}
@main
struct MyAppWidget: Widget {
let kind: String = "MyAppWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MyAppWidgetEntryView(entry: entry)
}
.configurationDisplayName("MyApp Widget")
.description("Track your project count")
.supportedFamilies([.systemSmall])
}
}
But again, what I'd really like to be able to do is access the attribute bodyText
or image1
from the Entity, like how my main app accesses it. E.g.,
VStack {
Image(uiImage: UIImage(data: project.image1 ?? self.projectImage1)!)
.resizable()
.aspectRatio(contentMode: .fill)
Text("\(project.bodyText!)")
}
Here's the Widget.swift code that seems closer to what I'm trying to accomplish, but it fails on getting project.bodyText
with the error Value of type 'FetchedResults<Project>' has no member 'bodyText'
-- I can't figure out why it's not seeing the attributes for my entity Project
// MARK: Core Data
var managedObjectContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
var workingContext: NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = managedObjectContext
return context
}
var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "MyAppApp")
let storeURL = URL.storeURL(for: "group.com.me.MyAppapp", databaseName: "MyAppApp")
let description = NSPersistentStoreDescription(url: storeURL)
container.loadPersistentStores(completionHandler: { storeDescription, error in
if let error = error as NSError? {
print(error)
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
return container
}()
// MARK: WIDGET
struct Provider: TimelineProvider {
// CORE DATA
var moc = managedObjectContext
init(context : NSManagedObjectContext) {
self.moc = context
}
// WIDGET
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting 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: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
struct MyAppWidgetEntryView : View {
var entry: Provider.Entry
@FetchRequest(entity: Project.entity(), sortDescriptors: []) var project: FetchedResults<Project>
var body: some View {
Text("\(project.bodyText)"). <--------------------- ERROR HAPPENS HERE
}
}
@main
struct MyAppWidget: Widget {
let kind: String = "MyAppWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(context: managedObjectContext)) { entry in
MyAppWidgetEntryView(entry: entry)
.environment(\.managedObjectContext, managedObjectContext)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct MyAppWidget_Previews: PreviewProvider {
static var previews: some View {
MyAppWidgetEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
Am I close? Have you happened to get something similar working? Thanks so much!