I am working on a project to develop a iOS app and widget extension(iOS 17+ and Xcode 15), and using coreData to manage user data, I have a 'lastModify: Date' derived attribute for my user entity, however the in-memory persistent store is not allow to have derived properties has argument, it leads app to crash when I trying to create a dummy data for preview provider, the error message: UserInfoWidgetExtension crashed due to an uncaught exception 'NSInvalidArgumentException'. Reason: Core Data provided atomic stores do not support derived.
And my code:
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)
}
}
Eventually I remove the derived attribute and it work just fine, but I am wondering it is have other way to avoid this problem? Is there any way to preview coreData entity with derived attribute? Thank you.
Update 12/1:
The error message of crash is:
UserInfoWidgetExtension crashed due to an uncaught exception 'NSInvalidArgumentException'. Reason: Core Data provided atomic stores do not support derived.
And update the code of UserInfoWidget.swift.
Update 15/1: Thanks to @loremipsum suggestion and it work well with following code:
let dummyContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "UserInfoModel")
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 = UserInfo(context: container.viewContext)
dummy.name = "foo"
dummy.fullname = "dummy foo"
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)
There are 2 ways to have an in-memory container.
One is by using
NSInMemoryStoreTypeAnd the other is by setting the url to
nullThe first way is known to have many limitations such as cascading deletion, the second way is what Apple uses to create a preview container in the standard
PersistentControllerthat is created by Xcode.