我正在尝试从我的应用程序小部件扩展读取我的应用程序 CoreData 文件。
读取成功,但当我的应用程序成功读取数据时,数据获取始终返回空。
这是我的小部件类:
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:我几乎关注了 SOF 和苹果社区中有关他的问题的每个主题,并且尝试了几乎所有尝试从应用程序小部件读取 CoreData 的解决方案,所有尝试都失败了,上下文中的结果为空。当我 100% 确定数据库已充满记录时。
仅设置目标会员资格是不够的。这只会将实体暴露给 Widget 代码。它不与 Widget 扩展共享底层数据库。 0
您需要为应用程序和扩展程序设置一个相同的应用程序组。然后为应用程序组创建数据库 url,并在小部件和应用程序中使用该数据库。
例如:
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
}
但请注意:该数据库将为空。如果您以前的数据库中已有数据,则必须迁移。