Hello,
I have a SwiftUI view with the following state variable:
@State private var startDate: Date = Date()
@State private var endDate: Date = Date()
@State private var client: Client? = nil
@State private var project: Project? = nil
@State private var service: Service? = nil
@State private var billable: Bool = false
Client, Project, and Service are all SwiftData models. I have some view content that binds to these values, including Pickers for the client/project/service and a DatePicker for the Dates.
I have an onAppear listener:
.onAppear {
switch state.mode {
case .editing(let tt):
Task {
await MainActor.run {
startDate = tt.startDate
endDate = tt.endDate
client = tt.client
project = tt.project
service = tt.service
billable = tt.billable
}
}
default:
return
}
}
This works as expected. However, if I remove the Task & MainActor.run, the values do not fully update. The DatePickers show the current date, the Pickers show a new value but tapping on them shows a nil default value.
What is also extremely strange is that if tt.billable is true, then the view does update as expected.
I am using Xcode 15.4 on iOS simulator 17.5. Any help would be appreciated.
SwiftData
RSS for tagSwiftData is an all-new framework for managing data within your apps. Models are described using regular Swift code, without the need for custom editors.
Posts under SwiftData tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi all, I am working on a macOS/iOS sandboxed app with SwiftUI/SwiftData. The app saves its' data into a ModelDocument file. But I want to also save large binary data files into this file.
I create the DocumentGroup scene with:
DocumentGroup(editing: Entry.self, contentType: .myDocument) {
MainWindowView()
}
In my MainWindowView, I use
@Environment(\.documentConfiguration) private var documentConfiguration
to get the URL to the user created ModelDocument file URL.
When I need to add large binary file into the ModelDocument file, I call a method in my model:
func saveReferencedData(_ data: Data, documentURL: URL?) throws {
let logger = Logger(subsystem: "saveReferencedData", category: "Asset")
if let documentURL {
let referencedFileName = "\(entryIdentifier)_\(assetIdentifier).\(assetType)"
let tempFileURL = documentURL.appending(components: referencedFileName)
if documentURL.startAccessingSecurityScopedResource() {
do {
try data.write(to: tempFileURL, options: [])
} catch {
documentURL.stopAccessingSecurityScopedResource()
throw AssetFileOperationError.unableToSaveReferenceFile
}
self.referencedFileLocation = referencedFileName
logger.debug("Successfully saved image data to: \(referenceFileURL)")
}
documentURL.stopAccessingSecurityScopedResource()
} else {
logger.debug("ERROR! Unable to save referenced image file because document URL is nil.")
}
}
When this method is called, the data file is saved, but immediately I gets this diablog box:
If I click on any of the options in the dialog box, the binary data file is removed.
I think this is a permission problem and I am not writing to the ModelDocument file correctly. But I can not find any information online to experiment more.
Does anyone have experience with a customized ModelDocument file with more data files written into it? Or if you have any insight or advice, it would be greatly appreciated.
Thank you so much for reading.
I am unsure the correct way to model my data.
I have a swift data object and then some referenced objects. Is there ever a reason those should just be structs or should they always be swift data objects themselves?
for example right now this is what I'm doing:
@Model
final class Exam {
var timestamp: Date
@Attribute(.unique) var examID: UUID
var title: String
var questions: [Question]
...
}
and Question is
struct Question: Codable, Identifiable {
var id: UUID
var number: Int
var points: Int
var prompt: String
var answer: String
}
is there any problem with this or should I not be using a Struct for Question and instead use another Swift Data object with @Relationship ?
I thought since its a simple object just using a struct would be fine, however...
when I create a new Question object, it seems to create SwiftUI retain cycles with the warning
=== AttributeGraph: cycle detected through attribute 633984 ===
in the terminal
for example,
Button("Add Question", systemImage: "questionmark.diamond") {
let newQuestion = Question(id: UUID(), number: exam.questions.count+1, points: 1, prompt: "", answer: "", type: .multipleChoice)
exam.questions.append(newQuestion)
}
So, is it ok to mix structs with swift data objects or is it not best practice?
And is this causing the SwiftUI retain cycles or are the issues unrelated?
I am creating an app, and on the app, the user can select something like, let's say, their workplace, while creating their account. I want to append the workplace data to a model without the use of UI, so that I can display the workplace names that the user can choose from. How do I append the data to the model?
The SwiftData predicate documentation says that it supports the contains(where:) sequence operation in addition to the contains(_:) string comparison but when I put them together in a predicate to try and perform a tokenised search I get a runtime error.
Unsupported subquery collection expression type (NSInvalidArgumentException)
I need to be able to search for items that contain at least one of the search tokens, this functionality is critical to my app. Any suggestions are appreciated. Also does anyone with experience with CoreData know if this is possible to do in CoreData with NSPredicate?
import SwiftData
@Model
final class Item {
var textString: String = ""
init() {}
}
func search(tokens: Set<String>, context: ModelContext) throws -> [Item] {
let predicate: Predicate<Item> = #Predicate { item in
tokens.contains { token in
item.textString.contains(token)
}
}
let descriptor = FetchDescriptor(predicate: predicate)
return try context.fetch(descriptor)
}
I am a develop beginner. Recently, my App used SwiftData's MigraitonPlan, which caused it to crash when I opened it for the first time after updating in TestFlight or the store. After clicking it a second time, I could enter the App normally, and I could confirm that all the models were the latest versions.However, when I tested it in Xcode, everything was normal without any errors.
Here is my MigrationPlan code:
import Foundation
import SwiftData
enum MeMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[MeSchemaV1.self, MeSchemaV2.self, MeSchemaV3.self, MeSchemaV4.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3, migrateV3toV4]
}
//migrateV1toV2, because the type of a data field in MeSchemaV1.TodayRingData.self was modified, the historical data was deleted during the migration, and the migration work was successfully completed.
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: MeSchemaV1.self,
toVersion: MeSchemaV2.self,
willMigrate: { context in
try context.delete(model: MeSchemaV1.TodayRingData.self)
},
didMigrate: nil
)
//migrateV2toV3, because a new Model was added, it would crash at startup when TF and the official version were updated, so I tried to delete the historical data during migration, but the problem still exists.
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: MeSchemaV2.self,
toVersion: MeSchemaV3.self,
willMigrate: { context in
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.HealthDataStatistics.self)
try context.delete(model: MeSchemaV2.SportsDataStatistics.self)
try context.delete(model: MeSchemaV2.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.TodayHealthData.self)
try context.delete(model: MeSchemaV2.SleepDataSource.self)
try context.delete(model: MeSchemaV2.WorkoutTargetData.self)
try context.delete(model: MeSchemaV2.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV2.HealthDataList.self)
},
didMigrate: nil
)
//migrateV3toV4, adds some fields in MeSchemaV3.WorkoutList.self, and adds several new Models. When TF and the official version are updated, it will crash at startup. Continue to try to delete historical data during migration, but the problem still exists.
static let migrateV3toV4 = MigrationStage.custom(
fromVersion: MeSchemaV3.self,
toVersion: MeSchemaV4.self,
willMigrate: { context in
do {
try context.delete(model: MeSchemaV3.WorkoutList.self)
try context.delete(model: MeSchemaV3.HealthDataStatistics.self)
try context.delete(model: MeSchemaV3.SportsDataStatistics.self)
try context.delete(model: MeSchemaV3.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV3.TodayRingData.self)
try context.delete(model: MeSchemaV3.TodayHealthData.self)
try context.delete(model: MeSchemaV3.SleepDataSource.self)
try context.delete(model: MeSchemaV3.WorkoutTargetData.self)
try context.delete(model: MeSchemaV3.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV3.HealthDataList.self)
try context.delete(model: MeSchemaV3.SleepStagesData.self)
try context.save()
} catch {
print("Migration from V3 to V4 failed with error: \(error)")
throw error
}
},
didMigrate: nil
)
}
I'm distributing an iOS (17.4+) and visionOS (1.2+) app via TestFlight that's using SwiftData.
The most common crash by far is from SwiftData when deleting models from a context using a predicate (first snippet below), but so far I've been unable to reproduce it myself locally (second snippet below). I'm using SwiftData outside of SwiftUI views, via my own wrapper, and converting between my app models and SwiftData models.
Does anyone have any ideas how I could potentially narrow this issue down (or reproduce), or know of any similar issues?
Thanks!
— Seb
do {
try context.transaction { context in
let predicate = #Predicate<PersistedFeed> {
$0.id == id
}
do {
try context.delete(model: PersistedFeed.self, where: predicate)
} catch {
// .. Omitted for brevity
}
}
} catch {
// .. Omitted for brevity
}
Crash:
Thread 0 Crashed:
0 libswiftCore.dylib 0x000000018dd558c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144)
1 SwiftData 0x000000022f7f323c static PersistentModel.keyPathToString(keypath:) + 1496 (DataUtilities.swift:0)
2 SwiftData 0x000000022f83312c PredicateExpressions.KeyPath.convert(state:) + 492 (FetchDescriptor.swift:394)
3 SwiftData 0x000000022f834a24 protocol witness for ConvertibleExpression.convert(state:) in conformance PredicateExpressions.KeyPath<A, B> + 16 (<compiler-generated>:0)
4 SwiftData 0x000000022f830a70 PredicateExpression.convertToExpressionOrPredicate(state:) + 724 (FetchDescriptor.swift:203)
5 SwiftData 0x000000022f831874 PredicateExpression.convertToExpression(state:) + 36 (FetchDescriptor.swift:217)
6 SwiftData 0x000000022f83b6c8 PredicateExpressions.Equal.convert(state:) + 328
7 SwiftData 0x000000022f8360ec protocol witness for ConvertibleExpression.convert(state:) in conformance PredicateExpressions.Equal<A, B> + 64 (<compiler-generated>:0)
8 SwiftData 0x000000022f830a70 PredicateExpression.convertToExpressionOrPredicate(state:) + 724 (FetchDescriptor.swift:203)
9 SwiftData 0x000000022f82fd60 PredicateExpression.convertToPredicate(state:) + 28 (FetchDescriptor.swift:224)
10 SwiftData 0x000000022f82edb4 nsPredicate<A>(for:) + 956 (FetchDescriptor.swift:88)
11 SwiftData 0x000000022f807c2c ModelContext.delete<A>(model:where:includeSubclasses:) + 596 (ModelContext.swift:1846)
12 SwiftData 0x000000022f81994c dispatch thunk of ModelContext.delete<A>(model:where:includeSubclasses:) + 56
Hi,
I am inserting two models where the "unique" attribute is the same. I was under the impression, that this should result in an upsert and not two inserts of the model, but that is not the case.
See the test coding below for what I am doing (it is self contained, so if you want to try it out, just copy it into a test target). The last #expect statement fails because of the two inserts. Not sure if this is a bug (Xcode 16 beta 2 on Sonoma running an iOS 18 simulator) or if I am missing something here...
// MARK: - UniqueItem -
@Model
final class UniqueItem {
#Unique<UniqueItem>([\.no])
var timestamp = Date()
var title: String
var changed = false
var no: Int
init(title: String, no: Int) {
self.title = title
self.no = no
}
}
// MARK: - InsertTests -
@Suite("Insert Tests", .serialized)
struct InsertTests {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
UniqueItem.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
@Test("Test unique.")
@MainActor func upsertAndModify() async throws {
let ctx = sharedModelContainer.mainContext
try ctx.delete(model: UniqueItem.self)
let item = UniqueItem(title: "Item \(1)", no: 0)
ctx.insert(item)
let allFD = FetchDescriptor<UniqueItem>()
let count = try ctx.fetchCount(allFD)
#expect(count == 1)
let updatedItem = UniqueItem(title: "Item \(1)", no: 0)
updatedItem.changed = true
ctx.insert(updatedItem)
// we should still have only 1 item because of the unique constraint
let allCount = try ctx.fetchCount(allFD)
#expect(allCount == 1)
}
}
There are multiple versions of VersionedSchema in my App. I used MigrationPlan to migrate data. It works well in Xcode, but in TestFlight and App Store, it always crashes when opening the App for the first time.
MeMigrationPlan Code:
import Foundation
import SwiftData
enum MeMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[MeSchemaV1.self, MeSchemaV2.self, MeSchemaV3.self, MeSchemaV4.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2, migrateV2toV3, migrateV3toV4]
}
//migrateV1toV2, because the type of a data field in MeSchemaV1.TodayRingData.self is modified, the historical data is deleted during migration, and the migration work is successfully completed.
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: MeSchemaV1.self,
toVersion: MeSchemaV2.self,
willMigrate: { context in
try context.delete(model: MeSchemaV1.TodayRingData.self)
},
didMigrate: nil
)
//migrateV2toV3, because a new Model was added, it would crash when starting up when TF and the official version were updated, so I tried to delete the historical data during migration, but the problem still exists.
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: MeSchemaV2.self,
toVersion: MeSchemaV3.self,
willMigrate: { context in
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.HealthDataStatistics.self)
try context.delete(model: MeSchemaV2.SportsDataStatistics.self)
try context.delete(model: MeSchemaV2.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV2.TodayRingData.self)
try context.delete(model: MeSchemaV2.TodayHealthData.self)
try context.delete(model: MeSchemaV2.SleepDataSource.self)
try context.delete(model: MeSchemaV2.WorkoutTargetData.self)
try context.delete(model: MeSchemaV2.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV2.HealthDataList.self)
},
didMigrate: nil
)
//migrateV3toV4, adds some fields in MeSchemaV3.WorkoutList.self, and adds several new Models. When TF and the official version are updated, it will crash at startup. Continue to try to delete historical data during migration, but the problem still exists.
static let migrateV3toV4 = MigrationStage.custom(
fromVersion: MeSchemaV3.self,
toVersion: MeSchemaV4.self,
willMigrate: { context in
do {
try context.delete(model: MeSchemaV3.WorkoutList.self)
try context.delete(model: MeSchemaV3.HealthDataStatistics.self)
try context.delete(model: MeSchemaV3.SportsDataStatistics.self)
try context.delete(model: MeSchemaV3.UserSettingTypeFor.self)
try context.delete(model: MeSchemaV3.TodayRingData.self)
try context.delete(model: MeSchemaV3.TodayHealthData.self)
try context.delete(model: MeSchemaV3.SleepDataSource.self)
try context.delete(model: MeSchemaV3.WorkoutTargetData.self)
try context.delete(model: MeSchemaV3.WorkoutStatisticsForTarget.self)
try context.delete(model: MeSchemaV3.HealthDataList.self)
try context.delete(model: MeSchemaV3.SleepStagesData.self)
try context.save()
} catch {
print("Migration from V3 to V4 failed with error: \(error)")
throw error
}
},
didMigrate: nil
)
}
Hello I'm a new developer and am learning the ropes. I have an app that I'm testing and seem to have run into a bug. The data is syncing from one device to another, however it takes closing the app on the Mac or force closing the app on iOS/iPadOS to get the app to reflect the new data.
Is there specific code I code share to help solve this issue or any suggestions that someone may have? Thank you ahead of time for your assistance.
import SwiftData
@main
struct ApplicantProcessorApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Applicant.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
struct ContentView: View {
var body: some View {
FilteredApplicantListView()
}
}
#Preview {
ContentView()
.modelContainer(SampleData.shared.modelContainer)
}
struct FilteredApplicantListView: View {
@State private var searchText = ""
var body: some View {
NavigationSplitView {
ApplicantListView(applicantFilter: searchText)
.searchable(text: $searchText, prompt: "Enter Name, Email, or Phone Number")
.autocorrectionDisabled(true)
} detail: { }
}
}
import SwiftData
struct ApplicantListView: View {
@Environment(\.modelContext) private var modelContext
@Query private var applicants: [Applicant]
@State private var newApplicant: Applicant?
init(applicantFilter: String = "") {
// Filters
}
var body: some View {
Group {
if !applicants.isEmpty {
List {
ForEach(applicants) { applicant in
NavigationLink {
ApplicantView(applicant: applicant)
} label: {
HStack {
VStack {
HStack {
Text(applicant.name)
Spacer()
}
HStack {
Text(applicant.phoneNumber)
.font(.caption)
Spacer()
}
HStack {
Text(applicant.email)
.font(.caption)
Spacer()
}
HStack {
Text("Expires: \(formattedDate(applicant.expirationDate))")
.font(.caption)
Spacer()
}
}
if applicant.applicationStatus == ApplicationStatus.approved {
Image(systemName: "checkmark.circle")
.foregroundStyle(.green)
.font(.title)
} else if applicant.applicationStatus == ApplicationStatus.declined {
Image(systemName: "xmark.circle")
.foregroundStyle(.red)
.font(.title)
} else if applicant.applicationStatus == ApplicationStatus.inProgress {
Image(systemName: "hourglass.circle")
.foregroundStyle(.yellow)
.font(.title)
} else if applicant.applicationStatus == ApplicationStatus.waitingForApplicant {
Image(systemName: "person.circle")
.foregroundStyle(.yellow)
.font(.title)
} else {
Image(systemName: "yieldsign")
.foregroundStyle(.yellow)
.font(.title)
}
}
}
}
.onDelete(perform: deleteItems)
}
} else {
ContentUnavailableView {
Label("No Applicants", systemImage: "pencil.fill")
}
}
}
.navigationTitle("Applicants")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addApplicant) {
Label("Add Item", systemImage: "plus")
}
}
}
.sheet(item: $newApplicant) { applicant in
NavigationStack {
ApplicantView(applicant: applicant, isNew: true)
}
}
}
private func addApplicant() {
withAnimation {
let newItem = Applicant()
modelContext.insert(newItem)
newApplicant = newItem
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(applicants[index])
}
}
}
func formattedDate(_ date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
return dateFormatter.string(from: date)
}
}
import SwiftData
@Model
final class Applicant {
var name = ""
var email = ""
var phoneNumber = ""
var applicationDate = Date.now
var expirationDate: Date {
return Calendar.current.date(byAdding: .day, value: 90, to: applicationDate)!
}
My app started crashing a ton with Xcode 16 beta 1 / iOS 18 because of "Thread 1: Fatal error: Never access a full future backing data". I was hoping this would be resolved with beta 2, but unfortunately this is not the case. I'm having a tough time reproducing this bug in a small sample project – I'd appreciate any hints as to what might be causing this.
Full error:
Thread 1: Fatal error: Never access a full future backing data - PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata://10A5A93C-DC7F-40F3-92DB-F4125E1C7A73/MyType/p2), implementation: SwiftData.PersistentIdentifierImplementation) with Optional(3BF44A2D-256B-4C40-AF40-9B7518FD9FE6)
Hi all, I have done a lot of research on this but am not able to come up with a workable solution.
Background:
I am trying to make an universal app on macOS/iOS that organizes media (image/video/pdf); think of its functionality like Apple's Photos app. So for my app document type, I would have my custom file package and within the package folder, there would be a SwiftData model container file and folders to hold user media.
Approaches taken:
DocumentGroup with SwiftData model then write directly into the file package
In my App scene, I create document by having DocumentGroup(editing: .customDocument, migrationPlan: CustomMigrationPlan.self). This will create a document with a model container with my SwiftData model. And when I need to add media files into the document, I get the URL of the document, then use FileManager to write the file into the desired folder in the document. The result is that the media file is saved in the file package but then the SwiftData container is corrupted (all model data is reset to empty.)
I am now trying to:
2. DocumentGroup with custom file package then try to embed SwiftData container
In my current approach, I would create a document by having DocumentGroup(newDocument: CustomFileDocument()). I have custom FileDocument and FileWrapper. But the problem is I don't know how to embed a SwiftData container into my FileDocument. Is it possible to create a SwiftData model container when my FilerWrapper initialize? I can't figure out how to do this.
Can anyone please advice on how I should accomplish this? Or if maybe I am looking at this problem wrongly? Do I need to use AppKit/UIKit with Core Data because it's currently not possible with SwiftUI/SwiftData?
Thank you so much for reading and any input is greatly appreciated.
I had a series of @Model classes with some mandatory attributes and some optional.
Pre-move to 18, everything was working fine. After the migration, it reports that every single non-Optional attribute is nil upon trying to save.
The error is CoreData related but not sure if its in the Core layer or Swift layer.
Sample error (with app data removed) is :
SwiftData.DefaultStore save failed with error: Error Domain=NSCocoaErrorDomain Code=1560 "Multiple validation errors occurred."
Error Domain=NSCocoaErrorDomain Code=1570 \"%{PROPERTY}@ is a required value.\" UserInfo={NSValidationErrorObject=<NSManagedObject: 0x30388b2a0>
NSLocalizedDescription=%{PROPERTY}@ is a required value., NSValidationErrorKey=systemName, NSValidationErrorValue=null}"
I have modified the code to provide default values for all constructors in an attempt to see a difference, but get the same errors
When user triggers a sheet of a view, it's quite easy to deal with its confirmation action. However, how can I deal with the cancellation action, which means when user taps the cancel button, what they edits can re-become its original version ( I am using SwiftData)?
Hi All,
Looking for some help.
I have an app that uses Charts and SwiftData.
I have created @Query that filters the records and charts displays Ints in a line graph by the date. Easy.
Most dates have two or more records.
What I need to do is add all the ints from the same day and use that result in the chart.
Any help you can send my way?
Thanks so much.
Blessings,
--Mark
I have a background thread that is updating a swift data model Item using a ModelActor. The background thread runs processing an Item and updates the Item's status field. I notice that if I have a view like
struct ItemListView: View {
@Query private var items: [Items]
var body: some View {
VStack {
ForEach(items) { item in
ItemDetailView(item)
}
}
}
}
struct ItemDetailView: View {
var item: Item
var body: some View {
// expected: item.status automatically updates when the background thread updates the `Item`'s `status`.
Text(item.status)
// actual: This text never changes
}
}
Then background updates to the Item's status in SwiftData does not reflect in the ItemDetailView. However, if I inline ItemDetailView in ItemListView like this:
struct ItemListView: View {
@Query private var items: [Items]
var body: some View {
VStack {
ForEach(items) { item in
// Put the contents of ItemDetailView directly in ItemListView
Text(item.status)
// result: item.status correctly updates when the background thread updates the item.
}
}
}
}
Then the item's status text updates in the UI as expected. I suspect ItemDetailView does not properly update the UI because it just takes an Item as an input. ItemDetailView would need additional understanding of SwiftData, such as a ModelContext.
Is there a way I can use ItemDetailView to show the Item's status and have the UI show the status as updated in the background thread?
In case details about my background thread helps solve the problem, my thread is invoked from another view's controller like
@Observable
class ItemCreateController {
func queueProcessingTask() {
Task {
let itemActor = ItemActor(modelContainer: modelContainer)
await itemActor.setItem(item)
await itemActor.process()
}
}
}
@ModelActor
actor ItemActor {
var item: Item?
func setItem(_ item: Item) {
self.item = modelContext.model(for: item.id) as? Item
}
func process() async {
// task that runs processing on the Item and updates the Item's status as it goes.
}
Some of our users on iOS 17.5+ started to encounter crash due to:
SwiftData/BackingData.swift:669: Fatal error: Unknown Relationship Key - subscription)
We have tried using SwiftData on the MainActor only but an issues still effects some of our users.
Our models look like these:
@Model
public final class ProfileModel {
public static let fetchDescriptor = FetchDescriptor<ProfileModel>.self
public enum Gender: Codable {
case female
case male
case other
}
public enum Units: Codable {
case imperial
case metric
}
public enum Eating: Codable {
case empath
case enthusiast
case explorer
case guardian
case harmonizer
case pacifier
case regulator
case stoic
case iosDefault
}
public enum RegistrationSource: Codable {
case iOS
case web
}
public enum NHE: Codable {
case combined
case emotional
case mindless
}
public enum Goal: Codable {
case beHealthier
case energyIncrease
case healthyHabits
case looseWeight
case relationshipsWithFood
}
public enum WeightLossFocus: Codable {
case activity
case healthyHabits
case nutrition
case other
}
@Attribute(.unique)
public let id: String
public let isPaid: Bool
public let eating: Eating
public let registrationSource: RegistrationSource
public let nhe: NHE?
public let firstName: String?
public let lastName: String?
public let gender: Gender?
public let height: String?
public let age: Int?
public let weight: String?
public let targetWeight: String?
public let bmi: String?
public let goal: Goal?
public let units: Units?
public let weightLossFocus: WeightLossFocus?
@Relationship(deleteRule: .cascade)
public var subscription: ProfileSubscriptionModel?
public init(
id: String,
isPaid: Bool,
eating: Eating,
registrationSource: RegistrationSource,
nhe: NHE?,
firstName: String?,
lastName: String?,
gender: Gender?,
height: String?,
age: Int?,
weight: String?,
targetWeight: String?,
bmi: String?,
goal: Goal?,
units: Units?,
weightLossFocus: WeightLossFocus?,
subscription: ProfileSubscriptionModel?
) {
self.id = id
self.isPaid = isPaid
self.eating = eating
self.registrationSource = registrationSource
self.nhe = nhe
self.firstName = firstName
self.lastName = lastName
self.gender = gender
self.height = height
self.age = age
self.weight = weight
self.targetWeight = targetWeight
self.bmi = bmi
self.goal = goal
self.units = units
self.weightLossFocus = weightLossFocus
self.subscription = subscription
}
}
@Model
public final class ProfileSubscriptionModel {
public static let fetchDescriptor = FetchDescriptor<ProfileSubscriptionModel>.self
public let price: Double
public let currency: String
public let period: Int
public let status: String
public let expirationDate: Date
public let cancelledAt: Date?
public init(price: Double, currency: String, period: Int, status: String, expirationDate: Date, cancelledAt: Date?) {
self.price = price
self.currency = currency
self.period = period
self.status = status
self.expirationDate = expirationDate
self.cancelledAt = cancelledAt
}
}
I have encountered an issue that when using a ModelActor to sync data in the background, the app will crash if one of the operations is to remove a PersistentModel from the context.
This is running on the latest beta of Xcode 16 with visionOS 1.2 as target and in Swift 6 language mode.
The code is being executed in a ModelActor.
The error is first thrown by:
#5 0x00000001c3223280 in PersistentModel.getValue<τ_0_0>(forKey:) ()
Thread 1: Fatal error: Context is missing for Optional(SwiftData.PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata://97AA86BC-475D-4509-9004-D1182ABA1922/Reminder/p303), implementation: SwiftData.PersistentIdentifierImplementation))
func globalSync() async {
await fetchAndSyncFolders()
let result = await fetchReminders()
switch result {
case .success(let ekReminders):
var localReminders = (try? await fetch(FetchDescriptor<Reminder>())) ?? []
// Handle local reminders with nil ekReminderID by creating new EKReminders for them
for reminder in localReminders {
if reminder.ekReminderID == nil {
await self.createEkReminder(reminder: reminder)
}
}
// Re-fetch local reminders to include newly created EKReminderIDs
localReminders = (try? await fetch(FetchDescriptor<Reminder>())) ?? []
var localReminderDict = [String: Reminder]()
for reminder in localReminders {
if let ekReminderID = reminder.ekReminderID {
if let existingReminder = localReminderDict[ekReminderID] {
self.delete(model: existingReminder)
} else {
localReminderDict[ekReminderID] = reminder
}
}
}
let ekReminderDict = createReminderLookup(byID: ekReminders)
await self.syncReminders(localReminders: Array(localReminderDict.values), localReminderDict: localReminderDict, ekReminderDict: ekReminderDict)
// Merge duplicates
await self.mergeDuplicates(localReminders: localReminders)
save()
case .failure(let error):
print("Failed to fetch reminders: \(error.localizedDescription)")
}
}
Sorry this post is duplicate
I was looking to the example code that point from What's new in SwiftData session and noticed something that I cannot understand about Swift concurrency. from this snippet:
static func createSampleData(into modelContext: ModelContext) {
Task { @MainActor in
let sampleDataTrips: [Trip] = Trip.previewTrips
let sampleDataLA: [LivingAccommodation] = LivingAccommodation.preview
let sampleDataBLT: [BucketListItem] = BucketListItem.previewBLTs
let sampleData: [any PersistentModel] = sampleDataTrips + sampleDataLA + sampleDataBLT
sampleData.forEach {
modelContext.insert($0)
}
if let firstTrip = sampleDataTrips.first,
let firstLivingAccommodation = sampleDataLA.first,
let firstBucketListItem = sampleDataBLT.first {
firstTrip.livingAccommodation = firstLivingAccommodation
firstTrip.bucketList.append(firstBucketListItem)
}
if let lastTrip = sampleDataTrips.last,
let lastBucketListItem = sampleDataBLT.last {
lastTrip.bucketList.append(lastBucketListItem)
}
try? modelContext.save()
}
}
From the code snippet, I cannot see any task that needs to be marked with await, and it is also marked as @MainActor.
My question is: why do we need to put Task here? It seems like all the code will run on the main thread anyway. I am just afraid that I might be missing some concept of Swift concurrency or SwiftData here.
Thank you