Starting point
I have an app that is in production that has a single entity called CDShift. This is the class:
@Model
final class CDShift {
var identifier: UUID = UUID()
var date: Date = Date()
...
}
This is how this model is written in the current version.
Where I need to go
Now, I'm updating the app and I have to do some modifications, that are:
add a new entity, called DayPlan
add the relationship between DayPlan and CDShift
What I did is this:
enum SchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[CDShift.self]
}
@Model
final class CDShift {
var identifier: UUID = UUID()
var date: Date = Date()
}
}
To encapsulate the current CDShift in a version 1 of the schema. Then I created the version 2:
enum SchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] {
[CDShift.self, DayPlan.self]
}
@Model
final class DayPlan {
var identifier: UUID = UUID()
var date: Date = Date()
@Relationship(inverse: \CDShift.dayPlan) var shifts: [CDShift]? = []
}
@Model
final class CDShift {
var identifier: UUID = UUID()
var date: Date = Date()
var dayPlan: DayPlan? = nil
}
}
The migration plan
Finally, I created the migration plan:
enum MigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self) { context in
// willMigrate, only access to old models
} didMigrate: { context in
// didMigrate, only access to new models
let shifts = try context.fetch(FetchDescriptor<SchemaV2.CDShift>())
for shift in shifts {
let dayPlan = DayPlan(date: shift.date)
dayPlan.shifts?.append(shift)
context.insert(dayPlan)
}
}
static var stages: [MigrationStage] {
print("MigrationPlan | stages called")
return [migrateV1toV2]
}
}
The ModelContainer
Last, but not least, how the model container is created in the App:
struct MyApp: App {
private let container: ModelContainer
init() {
container = ModelContainer.appContainer
}
var body: some Scene {
WindowGroup {
...
}
.modelContainer(container)
}
}
This is the extension of ModelContainer:
extension ModelContainer {
static var appContainer: ModelContainer {
let schema = Schema([
CDShift.self,
DayPlan.self
])
let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: Ecosystem.current.isPreview,
groupContainer: .identifier(Ecosystem.current.appGroupIdentifier)
)
do {
// let container = try ModelContainer(for: schema, configurations: modelConfiguration)
let container = try ModelContainer(for: schema, migrationPlan: MigrationPlan.self, configurations: modelConfiguration)
AMLogger.verbose("SwiftData path: \(modelConfiguration.url.path)")
return container
} catch (let error) {
fatalError("Could not create ModelContainer: \(error)")
}
}
}
The error
This has always worked perfectly until the migration. It crashes on the fatalError line, this is the error:
Unable to find a configuration named 'default' in the specified managed object model.
Notes
It seems that the version of the store is never updated to 2, but it keeps staying on 1. I tried also using the lightweight migration, no crash, it seems it recognizes the new entity, but the store version is always 1.
iCloud is enabled
I thought that the context used in the custom migration blocks is not the "right" one that I use when I create my container
If I use the lightweight migration, everything seems to work fine, but I have to manually do the association between the DayPlan and the CDShift objects
Do you have an idea on how to help in this case?
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
I'm trying to use SwiftData for a new app after ~20 years of Core Data (and EOF before that). So while I'm new to SwiftData, I'm not new to Apple persistence frameworks.
I've got a pretty typical workflow - need to load some JSON from the network, convert that into model objects.
I've created an actor using the @ModelActor macro and I'm using that to do the network fetch and insert. I can set breakpoints in this code and see that it does indeed run and it's running on a non-main queue. That all seems fine.
The problem is that my @Query powered variable in my SwiftUI user interface does not get updated when this data is loaded and saved as I would expect it to.
I'm passing in the container to the actor using modelContext.container from the same modelContext that is powering the view / in the environment. My understanding was that like Core Data before it, the SwiftData framework was listening for the relevant notifications passed by the container/context, processing those and updating the UI but I can only see my data if I quit and relaunch the app.
This seems like it should be a very common use case but I've not found much online. Not sure if that means I'm just doing something fundamentally wrong or what.
Tested on both iOS 18 and 17 with the same results.
Anyone else doing this successfully? What could I be doing wrong?
I'm currently syncing core data with the CloudKit private and public databases, as you can see in the code below, I'm saving the private database in the default configuration in Core Data and the public in a configuration called Public everything works fine when NSPersistentCloudKitContainer syncs, what I'm having an issue with is trying to save to the public data store PublicStore, for instance when I try to save with func createIconImage(imageName: String) it saves the image to the "default" store, not the PublicStore(Public configuration).
What could I do to make the createIconImage() function save to the PublicStore sqlite database?
class CoreDataManager: ObservableObject{
static let instance = CoreDataManager()
private let queue = DispatchQueue(label: "CoreDataManagerQueue")
@AppStorage(UserDefaults.Keys.iCloudSyncKey) private var iCloudSync = false
lazy var context: NSManagedObjectContext = {
return container.viewContext
}()
lazy var container: NSPersistentContainer = {
return setupContainer()
}()
init(inMemory: Bool = false){
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
}
func updateCloudKitContainer() {
queue.sync {
container = setupContainer()
}
}
private func getDocumentsDirectory() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}
private func getStoreURL(for storeName: String) -> URL {
return getDocumentsDirectory().appendingPathComponent("\(storeName).sqlite")
}
func setupContainer()->NSPersistentContainer{
let container = NSPersistentCloudKitContainer(name: "CoreDataContainer")
let cloudKitContainerIdentifier = "iCloud.com.example.MyAppName"
guard let description = container.persistentStoreDescriptions.first else{
fatalError("###\(#function): Failed to retrieve a persistent store description.")
}
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
if iCloudSync{
if description.cloudKitContainerOptions == nil {
let options = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudKitContainerIdentifier)
description.cloudKitContainerOptions = options
}
}else{
print("Turning iCloud Sync OFF... ")
description.cloudKitContainerOptions = nil
}
// Setup public database
let publicDescription = NSPersistentStoreDescription(url: getStoreURL(for: "PublicStore"))
publicDescription.configuration = "Public" // this is the configuration name
if publicDescription.cloudKitContainerOptions == nil {
let publicOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: cloudKitContainerIdentifier)
publicOptions.databaseScope = .public
publicDescription.cloudKitContainerOptions = publicOptions
}
container.persistentStoreDescriptions.append(publicDescription)
container.loadPersistentStores { (description, error) in
if let error = error{
print("Error loading Core Data. \(error)")
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container
}
func save(){
do{
try context.save()
//print("Saved successfully!")
}catch let error{
print("Error saving Core Data. \(error.localizedDescription)")
}
}
}
class PublicViewModel: ObservableObject {
let manager: CoreDataManager
@Published var publicIcons: [PublicServiceIconImage] = []
init(coreDataManager: CoreDataManager = .instance) {
self.manager = coreDataManager
}
func createIconImage(imageName: String) {
let newImage = PublicServiceIconImage(context: manager.context)
newImage.imageName = imageName
newImage.id = UUID()
save()
}
func save() {
self.manager.save()
}
}
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?
Can the cloudkit console team please look into managing indexes with their updated cloudkit console tool?
Working with Record indexes used to be straightforward but it has now become cumbersome and unintuitive.
For example, why does the tool force you to fill in some name field if adding a queryable index for recordName? Why can't you create several index types at once for a given field?
It is possible to manage schemas in some other ways but for small changes the console used to be handy. It's now become a pain.
Thanks!
Is this possible? Here's what I'm trying:
I'm making an app that reads from a CloudKit database. That's working fine.
I made a second "admin" type app to update the database. But, of course, I don't intend to release the admin app to the public.
So it was all working fine while testing in the development environment, but now that my public app is in TestFlight, and I have updated the necessary stuff that should allow me to write to production, but every attempt successfully writes to development, not production.
I'm wondering if I submitted my admin app to TestFlight if it would work then. But that doesn't seem like a long term solution, since I think I would have to re-upload every 90 days... just doesn't seem ideal or correct.
Do I HAVE to write the admin functionality in to the public app and hide it?
What are better ways I could write to production other than manually through the console?
Thanks everyone!
Hi,
Most of SwiftData tutorials use one SwiftData Class model though defining it is modelContainer is straightforward, what if I have say 10 model classes how to define them in the modelContainer ?
Kind Regards
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
)
}
Hi,
I have some small amount of users who are receiving a lot of "throttling" error messages from CloudKit when they try to upload / download data from CloudKit. I can see this from the user reports as well as the CloudKit dashboard. It's erratic, unpredictable, and is causing all sorts of bad experience in my app. I would like to understand this more:
what causes a particular user to 'throttle' vs others, and what can they do to avoid it?
as an e.g if we are uploading 4000 records, having split them up into a 100 CKModifyRecordsOperations with 400 records each ... would that result in some operations getting 'throttled' in the middle of the upload? Would all the operations receive the 'throttled' error message, or only some operations?
if I replay all the operations after the recommended timeout, could they also get a 'throttle' response?
how do I reproduce something like this in the development environment? With my testing and development so far, I haven't run into such an issue myself.
Would love to hear some insight and suggestions about how to handle this.
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)!
}
Hello!
Our team have been using a NSFetchedResultsController with viewContext for list UI.
But we found some UI Hang logs, and I'm guessing that it happens because of Faulting in viewContext.
The main thread call stacks in the logs look like below.
0 libsystem_kernel.dylib 0x00000001f378dbcc kevent_id + 8
1 libdispatch.dylib 0x00000001b4047b0f _dispatch_kq_poll + 227
2 libdispatch.dylib 0x00000001b40484df _dispatch_event_loop_wait_for_ownership + 435
3 libdispatch.dylib 0x00000001b4034983 __DISPATCH_WAIT_FOR_QUEUE__ + 339
4 libdispatch.dylib 0x00000001b403454b _dispatch_sync_f_slow + 147
5 CoreData 0x00000001b42859b3 _perform + 203
6 CoreData 0x00000001b4372767 -[NSManagedObjectContext+ 1275751 (_NestedContextSupport) newValuesForObjectWithID:withContext:error:] + 163
7 CoreData 0x00000001b424cf87 _PFFaultHandlerLookupRow + 275
8 CoreData 0x00000001b424cbbf _PF_FulfillDeferredFault + 191
9 CoreData 0x00000001b424cab7 _pvfk_header + 167
10 CoreData 0x00000001b424c9d7 _sharedIMPL_pvfk_core + 31
11 Works 0x000000010445517b closure #1 in static MailUIMailFactory.make(mail:needFolderName:) + 26530171 (MailUIMailFactory.swift:109)
...
0 libsystem_kernel.dylib 0x00000001dbd4254c kevent_id + 8
1 libdispatch.dylib 0x000000019b7057d3 _dispatch_kq_poll + 227
2 libdispatch.dylib 0x000000019b7061bb _dispatch_event_loop_wait_for_ownership + 435
3 libdispatch.dylib 0x000000019b6f2593 __DISPATCH_WAIT_FOR_QUEUE__ + 339
4 libdispatch.dylib 0x000000019b6f215b _dispatch_sync_f_slow + 147
5 CoreData 0x000000019b94f24b _perform + 203
6 CoreData 0x000000019ba3c41b -[NSManagedObjectContext+ 1274907 (_NestedContextSupport) newValueForRelationship:forObjectWithID:withContext:error:] + 167
7 CoreData 0x000000019b914387 -[NSFaultHandler retainedFulfillAggregateFaultForObject:andRelationship:withContext:] + 391
8 CoreData 0x000000019b9118bf -[_NSFaultingMutableSet willReadWithContents:] + 391
9 CoreData 0x000000019b91e663 -[_NSFaultingMutableSet count] + 23
10 Foundation 0x0000000192dbeb3f -[NSSet+ 7609151 (NSKeyValueSorting) sortedArrayUsingDescriptors:] + 55
11 Works 0x000000010249ec1f specialized static MailUIMailFactory.make(mail:needFolderName:) + 27470879 (MailUIMailFactory.swift:102)
...
We just use NSFetchedResultsController's fetchedObjects in table view's cellForRowAt method, and make a new model by MailUIMailFactory.make.
So we're thinking that we need to initialize NSFetchedResultsController with bg Context.
What you guys think?
Do I avoid using viewContext in heavy UI?
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
In an iOS viewController, I use the NSDiffableDataSource to populate a tableView from the results of a CoreData fetchController. The result class is defined by CoreData - in this case a class named CDFilterStack.
In xCode 16.0 Beta with strict concurrency checking = 'Complete' the CDFilterStack class has this warning everywhere it is referenced.
"Type 'CDFilterStack' does not conform to the 'Sendable' protocol; this is an error in the Swift 6 language mode."
The class definition of CDFilterStack is not editable because it is generated by CoreData.
I think I need to mark this class (and other similar classes) as @preconcurrency... but how & where?
Here's one method sample that generates three of these warnings
func initialSnapShot() -> NSDiffableDataSourceSnapshot<Int, CDFilterStack> {
var snapshot = NSDiffableDataSourceSnapshot<Int, CDFilterStack>()
if let sections = dataProvider.fetchedResultsController.sections {
for index in 0..<sections.count
{
let thisSection = sections[index]
guard let sectionStacks = thisSection.objects as? [CDFilterStack]
else { continue}
snapshot.appendSections([index])
snapshot.appendItems(sectionStacks)
} // for loop that will continue on error in objects
}
return snapshot
}
Careful reading of the various migration guides hasn't produced an example of how to handle this.. (btw the migration guides are nicely done:)
Hello, I have had an app that I created myself with Xcode for a long time and I also had it briefly for about two months as a beta in the App Store. At some point I had to take them out because I couldn't solve the problem with a server. I wanted to have a server via iCloud, Kit and Fire Base to upload and display content such as text images, videos. It should be an app that is very similar or the same as TikTok and Instagram just more data protection more 3-D visual design just a modern design and much better built than TikTok Instagram. Unfortunately, I didn't get it or almost Fire Base did it but the problem is that he always says yes this picture or this video or other error message were displayed, but among other things this error message was displayed. Hold this content can't be found in the server or something like that maybe someone here knows what she can do or knows about Fire Base maybe I did something wrong but actually I really taped everything at Fire Base and checked and switched everything on but nothing worked. I hope you can help me.
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.
}
I am using CloudKit+CoreData to store and sync and share my apps data. In my somewhat limited testing this is working quite well, but sync performance is unpredictable. Often it is very fast, sometimes it just stops until I put Mac app in background or restart. I guess from this thread this behavior is by design:
https://developer.apple.com/forums/thread/756315
I'm accepting that! :)
My plan is that I will use CloudKit+CoreData for source of truth syncing, but now I'm looking for an alternative channel that I can use to sync when multiple devices are working on this data at the same time. I think the basic design could be:
When a device starts editing a CKRecord
Post device IP and record ID to well known location/discovery service
Watch that location to see other devices that are editing that record
Make direct connection to those devices and sync through that connection (still also saving/merging with iCloud).
I think I know how to solve the data merge problems that will show up in this scenario, but I don't know what technologies I should use to create the sync channel. I'm looking for ideas. I don't want to run my own server.
SharePlay session seems almost perfect, but I'm not sure if it's really intended for this purpose. In particular I would want the session to start automatically (using participants from CKShare) without users having to manually join. Also I would like it to work with a single account (when I am viewing same data on my Mac and iOS device).
My other thought is that I would store active users+ip's in the synced CloudKit+CoreData store and then use Network.framework to connect and sync those active users. I think this could work, but is quite low level and might be a lot of work.
Are there other options that I'm missing or things I should think about?
Thanks,
Jesse
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
}
}