I'm trying to test migration between schemas but I cannot get it to work properly. I've never been able to get a complex migration to work properly unfortunately. I've removed a property and added 2 new ones to one of my data models.
This is my current plan.
enum MigrationV1toV2: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: { context in
print("Inside will migrate")
// Get old months
let oldMonths = try context.fetch(FetchDescriptor<SchemaV1.Month>())
print("Number of old months:\(oldMonths.count)")
for oldMonth in oldMonths {
// Convert to new month
let newMonth = Month(name: oldMonth.name, year: oldMonth.year, limit: oldMonth.limit)
print("Number of transactions in oldMonth: \(oldMonth.transactions?.count)")
print("Number of transactions in newMonth: \(newMonth.transactions?.count)")
// Convert transactions
for transaction in oldMonth.transactions ?? [] {
// Set action and direction
let action = getAction(from: transaction)
let direction = getDirection(from: transaction)
// Update category if necessary
var category: TransactionCategory? = nil
if let oldCategory = transaction.category {
category = TransactionCategory(
name: oldCategory.name,
color: SchemaV2.Category.Colors.init(rawValue: oldCategory.color?.rawValue ?? "") ?? .blue,
icon: getCategoryIcon(oldIcon: oldCategory.icon)
)
// Remove old category
context.delete(oldCategory)
}
// Create new
let new = Transaction(
date: transaction.date,
action: action,
direction: direction,
amount: transaction.amount,
note: transaction.note,
category: category,
month: newMonth
)
// Remove old transaction from month
oldMonth.transactions?.removeAll(where: { $0.id == transaction.id })
// Delete transaction from context
context.delete(transaction)
// Add new transaction to new month
newMonth.transactions?.append(new)
}
// Remove old month
context.delete(oldMonth)
print("After looping through transactions and deleting old month")
print("Number of transactions in oldMonth: \(oldMonth.transactions?.count)")
print("Number of transactions in newMonth: \(newMonth.transactions?.count)")
// Insert new month
context.insert(newMonth)
print("Inserted new month into context")
}
// Save
try context.save()
}, didMigrate: { context in
print("In did migrate")
let newMonths = try context.fetch(FetchDescriptor<SchemaV2.Month>())
print("Number of new months after migration: \(newMonths.count)")
}
)
static var stages: [MigrationStage] {
[migrateV1toV2]
}
}
It seems to run fine until it gets the the line: try context.save(). At this point it fails with the following line:
SwiftData/PersistentModel.swift:726: Fatal error: What kind of backing data is this? SwiftData._KKMDBackingData<Monthly.SchemaV1.Transaction>
Anyone know what I can do about this?
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
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I'm getting a crash in SwiftData but only on one specific device (iPhone 16 pro running 18.2 22C5131e) and not on an ipad or simulator
I cant troubleshoot this crash and its quite frustrating, all I am getting is
@Query(sort: \Todo.timestamp, order: .reverse) private var todos: [Todo]
ForEach(todos.filter { !$0.completed }) { item in // <---crash
TodoListView()
}
and the error is
Thread 1: signal SIGABRT
An abort signal terminated the process. Such crashes often happen because of an uncaught exception or unrecoverable error or calling the abort() function.
and
_SwiftData_SwiftUI.Query.wrappedValue.getter : τ_0_1
-> 0x105b98b58 <+160>: ldur x8, [x29, #-0x40]
0x105b98b5c <+164>: ldur x0, [x29, #-0x38]
0x105b98b60 <+168>: ldur x1, [x29, #-0x30]
0x105b98b64 <+172>: ldur x9, [x29, #-0x20]
0x105b98b68 <+176>: stur x9, [x29, #-0x28]
0x105b98b6c <+180>: ldr x8, [x8, #0x8]
0x105b98b70 <+184>: blr x8
0x105b98b74 <+188>: ldur x0, [x29, #-0x28]
0x105b98b78 <+192>: sub sp, x29, #0x10
0x105b98b7c <+196>: ldp x29, x30, [sp, #0x10]
0x105b98b80 <+200>: ldp x20, x19, [sp], #0x20
0x105b98b84 <+204>: ret
How do I fix this?
I get this red warning in Xcode every time my app is syncing to the iCloud. My model has only basic types and enum that conform to Codable so i'm not sure what is the problem.
App is working well, synchronization works. But the warning doesn't look good.
Maybe someone has idea how to debug it.
I have been recently getting the following error seemingly randomly, when an event handler of a SwiftUI view accesses a relationship of a SwiftData model the view holds a reference to. I haven't yet found a reliable way of reproducing it:
SwiftData/BackingData.swift:866: Fatal error: This model instance was invalidated
because its backing data could no longer be found the store.
PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: COREDATA_ID_URL),
implementation: SwiftData.PersistentIdentifierImplementation)
What could cause this error? Could you suggest me a workaround?
Hi There,
I have a iOS App which has been published and purely managing data by SwiftData. I use following simple codes everywhere in Views:
...
@Query var items: [Item]
....
if let firstItem = items.first( where: {...}) {
...
Then I encountered crash at Query that _items.wrapperdValue has some errors.
Then I tried to split first(where...) into ordinary way:
let filteredItems = items.filter(...)
if let firstItem = filteredItems.first {
...
It runs OK.
Is it a bug in SwiftData in 18.2 or I missed some steps to facilitate SwiftData macros?
I'm currently developing a SwiftUI application that utilizes SwiftData for data management. I am facing a challenge when trying to filter a query. Specifically, I want to filter a list of Item objects to match a Product instance that is passed to my View.
error :
Instance member 'product' cannot be used on type 'MainItemListEncap'; did you mean to use a value of this type instead
The view
//
// 311.1.1. MainRefToItem.swift
// ComparePrice
//
// Created by Herman VAN CAUWELAERT on 11/12/2024.
//
import SwiftUI
import SwiftData
struct MainItemListEncap: View {
@Bindable var product: Product
@Query(
filter: #Predicate { item in
item.productGroup == product
},
sort: [SortDescriptor(\Item.name)]
)
var items: [Item]
@Environment(\.modelContext) var modelContext
var body: some View {
ForEach(items) { item in
VStack(alignment: .leading) {
Text(item.name)
Text(item.description)
}
}
}
}
the product class.
import SwiftData
import Foundation
@Model
final class Product: CustomStringConvertible, CustomDebugStringConvertible {
@Attribute(.unique) var productName: String
var productDescription: String
var outputCurrency: String
// Gebruik een `String` als opslag voor `outputSystem`
var outputSystemRawValue: String = MeasurementSystem.metric.rawValue
// Computed property om `MeasurementSystem` te gebruiken
var outputSystem: MeasurementSystem {
get {
MeasurementSystem(rawValue: outputSystemRawValue)
}
set {
outputSystemRawValue = newValue.rawValue
}
}
// er zijn verschillend item versies voor een product
// als er een hoofdproduct gedelete wordt, dan zullen alle onderliggende items ook gedelete worden
@Relationship(deleteRule: .cascade, inverse: \Item.productGroup) var refToItems = [Item]()
init(productName: String = "", productDescription: String = "what is this product", outputCurrency: String = "EUR", outputSystem: MeasurementSystem = MeasurementSystem.metric) {
self.productName = productName
self.productDescription = productDescription
self.outputCurrency = outputCurrency
self.outputSystem = outputSystem
}
}
the item class
import Foundation
import SwiftData
@Model
final class Item: CustomStringConvertible, CustomDebugStringConvertible {
var timestamp: Date
@Attribute(.unique) var name: String
var productGroup: Product?
var shop: String //TODO: becomes Shop
var price: Double
var currency: String
var quantity: Double
var unit: String //TODO: becomes Unit
var isShown : Bool
var minimumQuantity: String
var rateStr: String {
conversionRate != nil ? " (rate: \(ValueFormatter.shared.format(conversionRate!)))" : ""
}
init(timestamp: Date = Date()
, name: String
, shop: String = "Colruyt"
, price:Double = 1.00
, currency: String = "EUR"
, quantity: Double = 1.0
, unit: String = "g"
, isShown:Bool = true
, conversionRate: Decimal? = 1
, minimumQuantity: String = "1"
) {
self.timestamp = timestamp
self.name = name
self.shop = shop
self.price = price
self.currency = currency
self.quantity = quantity
self.unit = unit
self.isShown = isShown
self.conversionRate = conversionRate
self.minimumQuantity = minimumQuantity
}
}
Hi,
I'm building a habit tracking app for iOS and macOS. I want to use up to date technologies, so I'm using SwiftUI and SwiftData.
I want to store user data locally on device and also sync data between device and iCloud server so that the user could use the app conveniently on multiple devices (iPhone, iPad, Mac).
I already tried SwiftData + NSPersistentCloudKitContainer, but I need to control when to sync data, which I can't control with NSPersistentCloudKitContainer. For example, I want to upload data to server right after data is saved locally and download data from server on every app open, on pull-to-refresh etc. I also need to monitor sync progress, so I can update the UI and run code based on the progress. For example, when downloading data from server to device is in progress, show "Loading..." UI, and when downloading finishes, I want to run some app business logic code and update UI.
So I'm considering switching from NSPersistentCloudKitContainer to CKSyncEngine, because it seems that with CKSyncEngine I can control when to upload and download data and also monitor the progress.
My database schema (image below) has relationships - "1 to many" and "many to many" - so it's convenient to use SwiftData (and underlying CoreData).
Development environment: Xcode 16.1, macOS 15.1.1
Run-time configuration: iOS 18.1.1, macOS 15.1.1
My questions:
1-Is it possible to use SwiftData for local data storage and CKSyncEngine to sync this local data storage with iCloud?
2-If yes, is there any example code to implement this?
I've been studying the "CloudKit Samples: CKSyncEngine" demo app (https://github.com/apple/sample-cloudkit-sync-engine), but it uses a very primitive approach to local data storage by saving data to a JSON file on disk.
It would be very helpful to have the same demo app with SwiftData implementation!
3-Also, to make sure I don't run into problems later - is it okay to fire data upload (sendChanges) and download (fetchChanges) manually with CKSyncEngine and do it often? Are there any limits how often these functions can be called to not get "blocked" by the server?
4-If it's not possible to use SwiftData for local data storage and CKSyncEngine to sync this local data storage with iCloud, then what to use for local storage instead of SwiftData to sync it with iCloud using CKSyncEngine? Maybe use SwiftData with the new DataStore protocol instead of the underlying CoreData?
All information highly appreciated!
Thanks,
Martin
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
Cloud and Local Storage
SwiftUI
SwiftData
After the problems with the ModelActor in iOS 18, it seemed like the ModelActor became more stable with iOS 18.1 and macOS 15.1.
However, I’m still encountering many problems and crashes. I wanted to ask if these issues are related to my persistence layer architecture or if they’re still inherent to the ModelActor itself.
I’ve generally followed the blog posts:
https://fatbobman.com/en/posts/practical-swiftdata-building-swiftui-applications-with-modern-approaches/
and
https://brightdigit.com/tutorials/swiftdata-modelactor/
and aim to achieve the following:
I have a single DataProvider that holds the ModelContainer and uses it to configure and initialize a single DataHandler. These are created once at app launch and injected into the SwiftUI view hierarchy as EnvironmentObjects. Since I need to access the SwiftData models not only in SwiftUI but also indirectly in ViewModels or UIKit views, all read operations on the models should go through the DataProvider ( ModelContainrs MainContext), while all other CRUD operations are handled centrally via the single DataHandler (executed within a single ModelActor).
Additionally, I want to monitor the entire container using another ModelActor, initialized in the DataProvider, which tracks changes to objects using TransactionHistory.
I’ve managed to implement this to some extent, but I’m facing two main issues:
ModelActor and Main Actor Requirement
The ModelActor only updates SwiftUI views when initialized via the main context of the ModelContainer and therefore runs on the Main Actor. It would be ideal for this to work in the background, but the issue with the ModelActor that existed previously doesn’t seem to have been resolved in iOS 18.1/macOS 15.1—am I wrong about this?
Frequent Crashes (more severe)
Crashes occur, especially when multiple windows on macOS or iPadOS access the same DataHandler to update models. This often leads to crashes during read operations on models by a SwiftUI view, with logs like:
Object 0x111f15480 of class _ContiguousArrayStorage deallocated with non-zero retain count 3. This object's deinit, or something called from it, may have created a strong reference to self which outlived deinit, resulting in a dangling reference.
error: the replacement path doesn't exist: "/var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift"
Can't show file for stack frame : <DBGLLDBStackFrame: 0x34d28e170> - stackNumber:1 - name:Tag.uuID.getter. The file path does not exist on the file system: /var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift
This error usually happens when there are multiple concurrent accesses to the DataHandler/ModelActor. However, crashes also occur sporadically during frequent accesses from a single view with an error like "the replacement path doesn't exist."
It also seems like having multiple ModelActors, as in this case (one for observation and one for data changes), causes interference and instability. The app appears to crash less frequently when the observer is not initialized, but I can’t verify this—it might just be a coincidence.
My Question:
Am I fundamentally doing something wrong with the ModelActors or the architecture of my persistence layer?
I have created an actor for the ModelContainer, in order to perform a data load when starting the app in the background. For this I have conformed to the ModelActor protocol and created the necessary elements, even preparing for test data.
Then I create a function of type async throws to perform the database loading processes and everything works fine, in that the data is loaded and when loaded it is displayed reactively.
actor Container: ModelActor {
nonisolated let modelContainer: ModelContainer
nonisolated let modelExecutor: ModelExecutor
static let modelContainer: ModelContainer = {
do {
return try ModelContainer(for: Empleados.self)
} catch {
fatalError()
}
}()
let context: ModelContext
init(container: ModelContainer = Container.modelContainer) {
self.modelContainer = container
let context = ModelContext(modelContainer)
self.modelExecutor = DefaultSerialModelExecutor(modelContext: context)
self.context = context
Task {
do {
try await loadData()
} catch {
print("Error en la carga \(error)")
}
}
}
}
The problem is that, in spite of doing the load inside a Task and that there is no problem, when starting the app it stops responding the UI while loading to the user interactions. Which gives me to understand that actually the task that should be in a background thread is running somehow over the MainActor.
As I have my own API that will provide the information to my app and refresh it at each startup or even send them in Batch when the internet connection is lost and comes back, I don't want the user to be continuously noticing that the app stops because it is performing a heavy process that is not really running in the background.
Tested and compiled on Xcode 15 beta 7.
I made a Feedback for this: FB13038621.
Thanks
Julio César
Hello! 😊
I currently manage an app called MoneyKeeper that uses SwiftData for its data storage framework. Many users have requested a "sharing" feature, but unfortunately, SwiftData does not yet support this functionality, and it’s unclear when it will. 😭
Initially, I considered using CloudKit and CKSyncEngine to implement quick synchronization and sharing. However, due to the complexity of the current data model’s relationships, modeling it with CloudKit’s schema alone seemed overly complicated and likely to introduce bugs.
As a result, I’ve decided to implement the sharing feature using CoreData and CloudKit. My plan to avoid conflicts with SwiftData includes:
Keeping the local storage locations for SwiftData and CoreData separate.
Using entirely separate CloudKit containers for SwiftData and CoreData.
I believe these measures will minimize potential issues, but I’m wondering if there’s anything else I should consider.
Using both SwiftData (for the personal database) and CoreData (for the shared database) feels like it could lead to significant technical debt in the future, and I anticipate encountering even more challenges during actual implementation.
I’d greatly appreciate your valuable insights on this matter. 🙏
The app MoneyKeeper, currently operated using SwiftData.
https://apps.apple.com/app/id6514279917
I have this actor
actor ConcurrentDatabase: ModelActor {
nonisolated let modelExecutor: any ModelExecutor
nonisolated let modelContainer: ModelContainer
init(modelContainer: ModelContainer) {
self.modelExecutor = DefaultSerialModelExecutor(modelContext: ModelContext(modelContainer))
self.modelContainer = modelContainer
}
/// Save pending changes in the model context.
private func save() {
if self.modelContext.hasChanges {
do {
try self.modelContext.save()
} catch {
...
}
}
}
}
I am getting a runtime crash on:
try self.modelContext.save()
when trying to insert something into the database and save
Thread 1: Fatal error: Incorrect actor executor assumption; Expected same executor as MainActor.
Can anyone explain why this is happening?
I have already built an app using SwiftData and now I want to introduce CloudKit features in it, what should I do? I'm totally new to this part. Thank you!
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
Cloud and Local Storage
SwiftData
After the significant issues with the ModelActor in iOS 18, it seemed like the ModelActor became more stable with iOS 18.1 and macOS 15.1.
However, I’m still encountering problems and crashes. I wanted to ask if these issues are related to my persistence layer architecture or if they’re still inherent to the ModelActor itself.
I’ve generally followed the blog posts:
https://fatbobman.com/en/posts/practical-swiftdata-building-swiftui-applications-with-modern-approaches/
and
https://brightdigit.com/tutorials/swiftdata-modelactor/
and aim to achieve the following:
I have a single DataProvider that holds the ModelContainer and uses it to configure and initialize a single DataHandler. These are created once at app launch and injected into the SwiftUI view hierarchy as EnvironmentObjects. Since I need to access the SwiftData models not only in SwiftUI but also indirectly in ViewModels or UIKit views, all read operations on the models should go through the DataProvider (and its MainContext), while all other CRUD operations are handled centrally via the single DataHandler (executed within a single ModelActor).
Additionally, I want to monitor the entire container using another ModelActor, initialized in the DataProvider, which tracks changes to objects using TransactionHistory.
I’ve managed to implement this to some extent, but I’m facing two main issues:
1. ModelActor and Main Actor Requirement
The ModelActor only updates SwiftUI views when initialized via the maincontext of the ModelContainer and therefore runs on the Main Actor. It would be ideal for this to work in the background, but the issue with the ModelActor that existed previously doesn’t seem to have been resolved in iOS 18.1/macOS 15.1—am I wrong about this?
2. Frequent Crashes (more severe)
Crashes occur, especially when multiple windows on macOS or on iPad access the same DataHandler to update models. This often leads to crashes during read operations on models by a SwiftUI view (but not only), with logs like:
error: the replacement path doesn't exist: "/var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift"
Can't show file for stack frame : <DBGLLDBStackFrame: 0x34d28e170> - stackNumber:1 - name:Tag.uuID.getter. The file path does not exist on the file system: /var/folders/gs/8rwdjczj225d1pj046w3d97c0000gn/T/swift-generated-sources/@__swiftmacro_12SwiftDataTSI3TagC4uuID18_PersistedPropertyfMa_.swift
This error usually happens when there are multiple concurrent accesses to the DataHandler/ModelActor. However, crashes also occur sporadically during frequent accesses from a single view with an error like "the replacement path doesn't exist."
It also seems like having multiple ModelActors, as in this case (one for observation and one for data changes), causes interference and instability. The app appears to crash less frequently when the observer is not initialized, but I can’t verify this—it might just be a coincidence.
My Question:
Am I fundamentally doing something wrong with the ModelActors or the architecture of my persistence layer?
A fairly simple ModelContainer:
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Model1.self,
Model2.self,
Model3.self
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false, cloudKitDatabase: .automatic)
do {
let container = try ModelContainer(for: schema, migrationPlan: MigrationPlan.self, configurations: [modelConfiguration])
return container
} catch {
fatalError("Error: Could not create ModelContainer: \(error)")
}
}()
After upgrading to macOS 15 and disabling/enabling iCloud for the app the sync stopped working on Mac. The steps:
Go to System Settings > Apple Account > iCloud > Saved to iCloud > See all
find the App and disable iCloud. After this synced items are removed from the app and some errors thrown in the console ('..unable to initialize without an iCloud account...')
Re-enable the iCloud setting
This error appears in the console:
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:](612): <NSCloudKitMirroringDelegate: 0x6000020dc1e0> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134415 "(null)"
On macOS Sonoma the items are synced back to the app and the sync is restored, but on Sequoia they don't come back and the sync is not working.
I tried resetting the container, deleting all data - no help.
Submitted FB15455847
Hey there,
I’m feeling pretty desperate at this point, as my most recent update to Xcode 16.1 and the new 18.1 simulators has basically made it impossible for me to work on my apps.
The same app and same code run fine in the 18.0 simulators with the same iCloud account logged in.
I’ve tried multiple simulators with the same results, even on different computers. I’ve also tried logging in repeatedly without any luck. The CloudKit database logs don’t show any errors or suspicious entries. Reinstalling the app on the simulator doesn't help either.
Whenever I launch the application in Xcode, I'm getting:
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _performSetupRequest:]_block_invoke(1240): <NSCloudKitMirroringDelegate: 0x600003d213b0>: Failed to set up CloudKit integration for store: <NSSQLCore: 0x103f124e0> (URL: file:///Users/kerstenbroich/Library/Developer/CoreSimulator/Devices/57BC78CE-DB2A-4AC0-9D7A-43C386305F56/data/Containers/Data/Application/EFDE9B05-0584-47C5-80AE-F2FF5994860C/Library/Application%20Support/Model.sqlite)
<CKError 0x600000d3dfe0: "Partial Failure" (2/1011); "Failed to modify some record zones"; partial errors: {
com.apple.coredata.cloudkit.zone:__defaultOwner__ = <CKError 0x600000d7c090: "Internal Error" (1/5000); "Failed user key sync">
}>
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate recoverFromError:](2310): <NSCloudKitMirroringDelegate: 0x600003d213b0> - Attempting recovery from error: <CKError 0x600000d3dfe0: "Partial Failure" (2/1011); "Failed to modify some record zones"; partial errors: {
com.apple.coredata.cloudkit.zone:__defaultOwner__ = <CKError 0x600000d7c090: "Internal Error" (1/5000); "Failed user key sync">
}>
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _recoverFromPartialError:forStore:inMonitor:]_block_invoke(2773): <NSCloudKitMirroringDelegate: 0x600003d213b0>: Found unknown error as part of a partial failure: <CKError 0x600000d7c090: "Internal Error" (1/5000); "Failed user key sync">
error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate _recoverFromPartialError:forStore:inMonitor:](2820): <NSCloudKitMirroringDelegate: 0x600003d213b0>: Error recovery failed because the following fatal errors were found: {
"<CKRecordZoneID: 0x600000d62340; zoneName=com.apple.coredata.cloudkit.zone, ownerName=__defaultOwner__>" = "<CKError 0x600000d7c090: \"Internal Error\" (1/5000); \"Failed user key sync\">";
}
Any help/ideas would be much appreciated, because I have no clue what to try next.
Thanks a lot!
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags:
CloudKit
Core Data
CloudKit Dashboard
SwiftData
I have ran into an issue that is illustrated by the code in the following GitHub repository.
https://github.com/dougholland/ColorTest
When a SwiftUI color originates from the ColorPicker it can be persisted correctly and renders the same as the original color. When the color originates from the MapFeature.backgroundColor, it is always rendered with the light appearance version of the color and not the dark appearance version. The readme in the GitHub repo has screenshots that show this.
Any assistance would be greatly appreciated as this is affecting an app that is in development and I'd like to resolve this before the app is released.
If this is caused by a framework bug, any possible workaround would be greatly appreciated also. I suspect it maybe a framework issue, possibly with some code related to the MapFeature.backgroundColor, because the issue does not occur when the color originates from the ColorPicker.
Are there any differences (either performance or memory considerations) between removing an array of model objects directly using .removeAll() vs using modelContext? Or, are they identical?
Attached below is an example to better illustrate the question (i.e., First Way vs Second Way)
// Model Definition
@Model
class GroupOfPeople {
let groupName: String
@Relationship(deleteRule: .cascade, inverse: \Person.group)
var people: [Person] = []
init() { ... }
}
@Model
class Person {
let name: String
var group: GroupOfPeople?
init() { ... }
}
// First way
struct DemoView: View {
@Query private groups: [GroupOfPeople]
var body: some View {
List(groups) { group in
DetailView(group: group)
}
}
}
struct DetailView: View {
let group: GroupOfPeople
var body: some View {
Button("Delete All Participants") {
group.people.removeAll()
}
}
// Second way
struct DemoView: View {
@Query private groups: [GroupOfPeople]
var body: some View {
List(groups) { group in
DetailView(group: group)
}
}
}
struct DetailView: View {
@Environment(\.modelContext) private var context
let group: GroupOfPeople
var body: some View {
Button("Delete All Participants") {
context.delete(model: Person.self, where: #Predicate { $0.group.name == group.name })
} // assuming group names are unique. more of making a point using modelContext instead
}
Hello, I’m upgrading my app from Core Data to SwiftData. Due to my old setup the Core Data store has an explicitly name like „Something.sqlite“, because it was defined via NSPersistentContainer(name: "Something") before switching to SwiftData.
Now my goal is to migrate the Core Data stack to SwiftData, while moving it to an App Group (for Widget support) as well as enable iCloud sync via CloudKit.
Working Migration without App Group & CloudKit
I’ve managed to get my migration running without migrating it to an App Group and CloudKit support like so:
@main
struct MyAppName: App {
let container: ModelContainer
init() {
// Legacy placement of the Core Data file.
let dataUrl = URL.applicationSupportDirectory.appending(path: "Something.sqlite")
do {
// Create SwiftData container with migration and custom URL pointing to legacy Core Data file
container = try ModelContainer(
for: Foo.self, Bar.self,
migrationPlan: MigrationPlan.self,
configurations: ModelConfiguration(url: dataUrl))
} catch {
fatalError("Failed to initialize model container.")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
How To Migrate to App Group & CloudKit?
I’ve already tried to use the ModelConfiguration with a name, but it seems to only look for a .store file and thus doesn’t copy over the Core Data contents.
let fullSchema = Schema([Foo.self, Bar.self])
let configuration = ModelConfiguration("Something", schema: fullSchema)
Can someone help me how to do this migration or point me into the right direction? I can’t find anything relating this kind of migration …
When the following models in SwiftData,
@Model
final class UndoRedoData {
var id: [Int]
init(id: [Int]) {
self.id = id
}
}
I created the following code.
struct ContentView: View {
@ObservedObject var swiftDataViewModel = SwiftDataArrayViewModel.shared
@State private var idArray: [Int] = [1,2,3,4]
@State private var firstviewSwich: Bool = true
@State private var twoviewSwich: Bool = false
@State private var threeviewSwich: Bool = false
var body: some View {
VStack {
if firstviewSwich == true {
Button(action: addItem) {
Text("1.New Item")
}
}
if twoviewSwich == true {
Button {
forArrayData()
} label: {
Text("2.Data Road")
}
}
if threeviewSwich == true {
Button(action: undoItem) {
Text("3.Undo")
}
}
}
}
private func addItem() {
withAnimation {
let newItem = UndoRedoData(id: [1,2,3,4])
swiftDataViewModel.taskContext.insert(newItem)
do {
try swiftDataViewModel.taskContext.save()
} catch {
print(error)
}
swiftDataViewModel.fetchItems()
firstviewSwich.toggle()
twoviewSwich.toggle()
}
}
private func forArrayData() {
twoviewSwich.toggle()
for data in idArray {
swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager)
}
threeviewSwich.toggle()
}
private func undoItem() {
swiftDataViewModel.arrayItemUndoManager.undo()
threeviewSwich.toggle()
firstviewSwich.toggle()
}
}
class SwiftDataArrayViewModel: ObservableObject {
static let shared = SwiftDataArrayViewModel()
let modelContainer: ModelContainer
@ObservationIgnored
lazy var taskContext: ModelContext = {
return ModelContext(modelContainer)
}()
@Published var arrayItems = [UndoRedoData]()
@Published var arrayItemUndoManager = UndoManager()
init() {
let schema = Schema([UndoRedoData.self])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError(error)
}
fetchItems()
}
func fetchItems() {
let fetchDescriptor = FetchDescriptor<UndoRedoData>()
do {
arrayItems = try taskContext.fetch(fetchDescriptor)
} catch {
fatalError(error)
}
}
func idUndoCreate(id: Int, undoManager: UndoManager?) {
undoManager?.registerUndo(withTarget: self) { target in
target.removeID()
}
}
func removeID() {
if let firstUndoRedoData = arrayItems.first {
print("Before Delete:\(firstUndoRedoData.id)")
if !firstUndoRedoData.id.isEmpty {
firstUndoRedoData.id.removeLast()
}
print("After Delete:\(firstUndoRedoData.id)")
}
do {
try taskContext.save()
} catch {
print(error)
}
fetchItems()
}
}
In this code, 1. Create an Item in New Item, 2. Execute Data Road and register the data in the array that is the same value as the data created in New Item in SwiftData one by one in UndoManager by for data in idArray.
This is done because the data in the array and the data created by New Item in SwiftData can be known in advance.
private func forArrayData() {
twoviewSwich.toggle()
for data in idArray {
swiftDataViewModel.idUndoCreate(id: data, undoManager: swiftDataViewModel.arrayItemUndoManager)
}
// class SwiftDataArrayViewModel: ObservableObject
func idUndoCreate(id: Int, undoManager: UndoManager?) {
undoManager?.registerUndo(withTarget: self) { target in
target.removeID()
}
}
After registering in UndoManager, when Undo is executed with 3. Undo, instead of being able to perform Undo where one id is deleted each time, all the data of the id in SwiftData is deleted in a one-time Undo.
I would like to be able to delete one id each time Undo is performed and restore them in sequence, but I can only delete them all once. Does this mean that such registration to UndoManager should not be done with for statements, etc.? Or is there another problem in the code?
I want to make sure that one id is deleted for each Undo executed.
Hi! I believe there might be a small bug in the SwiftData Quakes Sample App.^1 The Quakes app requests a JSON feed from USGS.^2 What seems to be breaking is that apparently earthquake entities from USGS can return with null magnitudes. That is throwing errors from the decoder:
struct GeoFeatureCollection: Decodable {
let features: [Feature]
struct Feature: Decodable {
let properties: Properties
let geometry: Geometry
struct Properties: Decodable {
let mag: Double
let place: String
let time: Date
let code: String
}
struct Geometry: Decodable {
let coordinates: [Double]
}
}
}
which is expecting mag to not be nil.
Here is my workaround:
struct GeoFeatureCollection: Decodable {
let features: [Feature]
struct Feature: Decodable {
let properties: Properties
let geometry: Geometry
struct Properties: Decodable {
let mag: Double?
let place: String
let time: Date
let code: String
}
struct Geometry: Decodable {
let coordinates: [Double]
}
}
}
And then:
extension Quake {
/// Creates a new quake instance from a decoded feature.
convenience init(from feature: GeoFeatureCollection.Feature) {
self.init(
code: feature.properties.code,
magnitude: feature.properties.mag ?? 0.0,
time: feature.properties.time,
name: feature.properties.place,
longitude: feature.geometry.coordinates[0],
latitude: feature.geometry.coordinates[1]
)
}
}