I am trying to read my app CoreData file from my app Widget extension .
The read was success but the data fetch is always return empty while my app is reading the data successfully .
here is Prayers Entity : ( Target membership is selected to widget and app )
and this is my widget class :
import WidgetKit
import SwiftUI
import CoreData
class CoreDataStack {
static let shared = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Prayers")
let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.xxx.test.xxx.test")!.appendingPathComponent("Prayers.sqlite")
let description = NSPersistentStoreDescription(url: storeURL)
description.shouldMigrateStoreAutomatically = true
description.shouldInferMappingModelAutomatically = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
})
return container
}()
}
struct widgetsEntryView : View {
var entry: Provider.Entry
@State private var lineY:CGFloat = 0
@State var zz:CDPrayer!
@State var zzzzzzzz:String = ":))"
var body: some View {
ZStack{
GeometryReader { proxy in
HStack(alignment: .center, spacing: 0){
if zz != nil {
prayerBiteVertical(prayer:zz,proxy:proxy,lineY : $lineY)
}else{
Text(zzzzzzzz)
.font(.system(size: 9))
}
}
} .coordinateSpace(name: "_geo")
.onChange(of: entry.date) { oldValue, newValue in
loadDate()
}.onAppear(){
loadDate()
}
}
}
func loadDate(){
let managedObjectContext = CoreDataStack.shared.persistentContainer.viewContext
let fetchRequest = CDPrayer.fetchRequest()
do{
let prayers = try managedObjectContext.fetch(fetchRequest)
zzzzzzzz = "ok \(prayers)"
//They r 5 prayers we r good
if prayers.count == 5 {
zz = prayers.filter{$0.prayer == "Fajr"}[0]
}
} catch let error as NSError {
zzzzzzzz = "[PrayerTimes][WIDGET] Could not fetch. \(error), \(error.userInfo)"
}
}
}
struct prayerBiteVertical : View {
@State var prayer:CDPrayer
@State var proxy:GeometryProxy!
@Binding var lineY:CGFloat
@State var btn:btton_themes = .normal
//Keys
@State var key_prayer:String = ""
var body: some View {
ZStack{
Color("highlightBg")
GeometryReader{ g in
Color.clear.onAppear(){
lineY = g.frame(in: .named("_geo")).minY
}
}.frame(height :0.1)
VStack{
Text(key_prayer)
.font(.poppins(.Regular,size: 15))
.minimumScaleFactor(0.85)
Image(systemName: "sunset.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width : 25)
.frame(height : 20)
Text("11:11")
.font(.poppins(.Regular,size: 18))
.minimumScaleFactor(0.85)
Spacer()
}
}.frame(width : proxy.frame(in: .local).width * 0.2)
.onChange(of: prayer) { oldValue, newValue in
key_prayer = prayer.prayer!
}
}
}
struct widget_Medium: Widget {
let kind: String = "widgets"
var body: some WidgetConfiguration {
AppIntentConfiguration(kind: kind, provider: Provider())
{ entry in
widgetsEntryView(entry: entry)
.containerBackground( .clear, for: .widget)
}.supportedFamilies([.systemMedium])
.containerBackgroundRemovable(true)
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationAppIntent
}
struct Provider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationAppIntent())
}
func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry {
SimpleEntry(date: Date(), configuration: configuration)
}
func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<SimpleEntry>
{
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 ..< 60
{
let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, configuration: configuration)
entries.append(entry)
}
return Timeline(entries: entries, policy: .atEnd)
}
}
#Preview(as: .systemMedium) {
widget_Medium()
} timeline: {
SimpleEntry(date: .now, configuration: ConfigurationAppIntent())
}
PS : I've followed almost every topic about his issue in SOF and apple community and I've tried almost every solution of trying to read CoreData from app widget all my attempts failed with empty results [] from the context . when I am 100% sure the database is full of records .
Setting the Target Membership is not enough. This will only expose the entities to the Widget code. It doesn´t share the underlying database with the Widget extension.
You need to set up an app group to be the same for both, the application and the extension. Then create the database url for the appgroup and use this database in both widget and app.
e.g.:
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "TestCoreData")
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app.group.identifier")?.appendingPathComponent("databasename.sqlite")
let storeDescription = NSPersistentStoreDescription(url: url!)
container.persistentStoreDescriptions = [storeDescription]
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
But be aware: This database will be empty. If you already have data in your previous database you will have to migrate.