我正在开发一个开发 iOS 应用程序和小部件扩展(iOS 17+ 和 Xcode 15)的项目,并使用 coreData 来管理用户数据,我的用户实体有一个“lastModify: Date”派生属性,但是-内存持久存储不允许派生属性有参数,当我尝试为预览提供程序创建虚拟数据时,它会导致应用程序崩溃,错误消息:
UserInfoWidgetExtension crashed due to an uncaught exception 'NSInvalidArgumentException'. Reason: Core Data provided atomic stores do not support derived
。
我的代码:
import WidgetKit
import SwiftUI
import Intents
import AppIntents
import CoreData
struct WidgetDetailsProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
return SimpleEntry(date: .now)
}
func snapshot(for configuration: UserInfoWidgetIntent, in context: Context) async -> SimpleEntry {
return SimpleEntry(date: .now)
}
func timeline(for configuration: UserInfoWidgetIntent, in context: Context) async -> Timeline<SimpleEntry> {
let entry = SimpleEntry(date: Date())
let timeline = Timeline(entries: [entry], policy: .never)
return timeline
}
}
struct UserInfoWidgetIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Test"
static var description: IntentDescription = IntentDescription("Test description")
@Parameter(title: "Display in full name")
var isDisplayFullname: Bool
init(isDisplayFullname: Bool) {
self.isDisplayFullname = isDisplayFullname
}
init() {}
}
struct SimpleEntry: TimelineEntry {
let date: Date
let isDisplayFullname: Bool
}
struct UserInfoWidgetEntryView : View {
var entry: WidgetDetailsProvider.Entry
@FetchRequest(sortDescriptors: [SortDescriptor(\.lastModify, order: .reverse)]) var userInfo: FetchedResults<UserInfo>
var body: some View {
VStack {
Text(entry.date, style: .time)
.frame(alignment: .leading)
HStack {
if userInfo.isEmpty {
Text("The list is Empty")
}
ForEach(userInfo) {user in
if entry.isDisplayFullname {
Text(user.fullname)
} else {
Text(user.name)
}
}
}
}.containerBackground(for: .widget) {
Color.white
}
}
}
struct UserInfoWidget: Widget {
let kind: String = "UserInfoWidgetIntent"
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: kind,
intent: UserInfoWidgetIntent.self,
provider: WidgetDetailsProvider()) { entry in
UserInfoWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
struct UserInfoWidget_Previews: PreviewProvider {
static var previews: some View {
let dummyContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "UserModel")
let description = NSPersistentStoreDescription()
description.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [description]
container.loadPersistentStores {description, error in
if let error = error {
print("Failed to load: \(error)")
}
}
let dummy = Barcode(context: container.viewContext)
dummy.username = "foo"
do {
try container.viewContext.save()
} catch {
fatalError("Failed to save entity.")
}
return container
}()
UserInfoWidgetEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemMedium))
.environment(\.managedObjectContext, dummyContainer.viewContext)
}
}
最终我删除了派生属性,它工作得很好,但我想知道是否有其他方法来避免这个问题?有没有办法预览具有派生属性的 coreData 实体?谢谢你。
12月1日更新: 崩溃的错误信息是:
UserInfoWidgetExtension crashed due to an uncaught exception 'NSInvalidArgumentException'. Reason: Core Data provided atomic stores do not support derived
。
并更新 UserInfoWidget.swift 的代码。
15/1更新: 谢谢@loremipsum的建议,它与以下代码配合得很好:
let dummyContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "BarcodeModel")
let description = NSPersistentStoreDescription()
//description.type = NSInMemoryStoreType
description.url = URL(fileURLWithPath: "/dev/null")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores {description, error in
if let error = error {
print("Failed to load: \(error)")
}
}
let dummy = Barcode(context: container.viewContext)
dummy.title = "dummy title"
dummy.textCode = "1234-abcd"
do {
try container.viewContext.save()
} catch {
fatalError("Failed to save entity.")
}
return container
}()
USerInfoWidgetEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
.environment(\.managedObjectContext, dummyContainer.viewContext)
有两种方法可以拥有内存容器。
一种是使用
NSInMemoryStoreType
description.type = NSInMemoryStoreType
另一种是将 url 设置为
null
description.url = URL(fileURLWithPath: "/dev/null")
第一种方式已知有很多限制,例如级联删除,第二种方式是Apple用来在Xcode创建的标准
PersistentController
中创建预览容器。