I'm working on an app that is using Core Data. I have a custom big number class that boils down to a double and an integer, plus math functions. I've been using transformable types to store these big numbers, but its forcing me to do a lot of ugly casts since the number class is used throughout the application. I figure I can either have my stored values be named differently (e.g. prefix with underscore) and have a computed variable to cast it to the correct type, or find some way to move the big number class to being an NSManagedObject. The issue with this is that the inverse relationships would be massive for the big number class, since multiple entities use the class in multiple properties already. Would it be recommended that I just keep using Transformable types and casts to handle this, or is there some standard way to handle a case like this in Core Data relationships?
iCloud & Data
RSS for tagLearn how to integrate your app with iCloud and data frameworks for effective data storage
Post
Replies
Boosts
Views
Activity
Anyone getting "Failed to execute query" when querying a Record type in CloudKit Console?
just a simple query on all records
Hello,
I'm currently developing an app using SwiftData.
I want the app to use CloudKit to sync data, so I made sure all my model properties are optional.
I've defined a Codable enum as follows:
enum Size: Int, Codable {
case small
case medium
case large
}
I've defined a Drink SwiftData model as follows:
@Model
class Drink {
var name: String?
var size: Size?
init(
name: String? = nil,
size: Size? = nil
) {
self.name = name
self.size = size
}
}
In one of my Views, I want to use a @Query to fetch the data, and use a Predicate to filter the data. The Predicate uses the size enumeration of the Drink model. Here is the code:
struct DrinksView: View {
@Query var drinks: [Drink]
init() {
let smallRawValue: Int = Size.small.rawValue
let filter: Predicate<Drink> = #Predicate<Drink> { drink in
if let size: Size = drink.size {
return size.rawValue == smallRawValue
} else {
return false
}
}
_drinks = Query(filter: filter)
}
var body: some View {
List {
ForEach(drinks) { drink in
Text(drink.name ?? "Unknown Drink")
}
}
}
}
The code compiles, but when I run the app, it crashes with the following error:
Thread 1: Fatal error: Couldn't find \Drink.size!.rawValue on Drink with fields [SwiftData.Schema.PropertyMetadata(name: "name", keypath: \Drink.name, defaultValue: nil, metadata: nil), SwiftData.Schema.PropertyMetadata(name: "size", keypath: \Drink.size, defaultValue: nil, metadata: nil)]
How can I filter my data using this optional variable on the Drink model?
Thanks,
Axel
In Xcode 15.0.1, I created a new project to start working with SwiftData. I did this by creating a default App project and checking the Use SwiftData checkbox. The resulting project contains just three files: an app entry point file, a ContentView SwiftUI view file, and an Item model file.
The only change I made was to annotate the default Item timestamp property with a .transformable attribute.
Here is the resulting model:
@Model
final class Item {
@Attribute(.transformable(by: TestVT.self)) var timestamp: Date // Only updated this line
init(timestamp: Date) {
self.timestamp = timestamp
}
}
And here is the definition of TestVT. It is a basic ValueTransformer that simply tries to store the Date as a NSNumber:
// Added this
class TestVT: ValueTransformer {
static let name = NSValueTransformerName("TestVT")
override class func transformedValueClass() -> AnyClass {
NSNumber.self
}
override class func allowsReverseTransformation() -> Bool {
true
}
override func transformedValue(_ value: Any?) -> Any? {
guard let date = value as? Date else {
return nil
}
let ti = date.timeIntervalSince1970
return NSNumber(value: ti)
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let num = value as? NSNumber else {
return nil
}
let ti = num.doubleValue as TimeInterval
return Date(timeIntervalSince1970: ti)
}
}
And finally, I made sure to register my ValueTransformer but updating the sharedModelContainer definition in the App:
var sharedModelContainer: ModelContainer = {
ValueTransformer.setValueTransformer(TestVT(), forName: TestVT.name) // Only added this line
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
Prior to Xcode 15.1, this was working fine. However, now when I try to create an item when running the app I get the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "timestamp"; desired type = NSNumber; given type = __NSTaggedDate; value = 2023-12-14 01:47:11 +0000.'
I'm unsure of why this stopped working. The error seems to be complaining about the input being of type Date when NSNumber was expected, but I thought that's what the ValueTransformer was supposed to be doing.
Important note: prior to Xcode 15.1, I did not originally override the transformedValueClass() and everything was working but in the new Xcode when launching the app I was getting a Thread 1: EXC_BAD_ACCESS (code=1, address=0x0) on the return try ModelContainer(...) line. Removing the .transformable property from my model fixed the issue. That's why I added the override here, because I think the docs indicate overriding it as well and I missed that the first time. This being said, I think the code I have is what a correct ValueTransformer would look like.
If anyone has experienced this issue, or has a working ValueTransformer for SwiftData in Xcode 15.1, please let me know. Appreciate any help with this issue. Thanks so much!
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.
}
All of a sudden my app started getting this CloudKit error, and it happens to a lot of users. I had no changes to cloud sync for months and really surprised by seeing this. What confuses me even more, is that there is no information on the web about this kind of error. I have no idea what causes it and how to solve it. Would love to get any feedback from the CloudKit engineer.
Client went away before operation 27761871408C460A could be validated; failing
{
"NSUnderlyingError": "<CKUnderlyingError 0x600002573f30: \"ClientInternalError\" (2005); \"Client went away before operation 27761871408C460A could be validated; failing\">",
"CKErrorDescription": "Client went away before operation 27761871408C460A could be validated; failing",
"NSDebugDescription": "CKInternalErrorDomain: 2005",
"NSLocalizedDescription": "Client went away before operation 27761871408C460A could be validated; failing"
}
Seems to happen only on macOS.
Using this Apple repository as a basis
https://github.com/apple/sample-cloudkit-zonesharing
I created and verified the shared zone and the same zone is private for the person who shared it and shared for the person who received it, so aren't they the same zones?
[same zone but different id?]
I can make the person who shared the zone (owner) access the zone as a .shared scope just like the person who was shared.
When using Core Data I would override willSave on NSManagedObject to compute alastModified value on a model object. This allowed one simple method to check changed values and set a date if necessary.
It is possible set lastModified in SwiftData, but the approaches I have found all have drawbacks when compared to the previous approach.
Hide saved model properties behind transient versions
private var textSaved: String = ""
var text: String {
get { textSaved }
set {
textSaved = newValue
lastModified = .now
}
}
I could hide every property that should update the lastModified behind a computed value, but this requires additional code for each new property and obfuscates the model definition.
Update all properties through an update function
func update<T>(keyPath: ReferenceWritableKeyPath<Player, T>, to value: T)
Paul Hudson notes a workaround where any changes are made to the model through an update function that takes a keyPath. This will add complexity to every view that wants to modify model properties, and also leaves those properties open to change through other approaches.
Use ModelContext.willSave
ModelContext sends a notification when it is about to save. This could be caught in .onReceive in a view or perhaps in some model-holding singleton, then ALL changes queried and dealt with accordingly. Perhaps the best approach here would to add willSave to all model objects so code external to the model isn't doing the lastModified logic.
This last solution feels like the best way forward that I know (ideally avoiding any .onReceive code in views). Should I prefer another solution or are there better ones I have missed?
Hello!
I deleted an index on the cloudkit console of a container in development environment and re-added it again with the same name. After that my app has been giving error on development environment.
The live version is working fine as I haven't deployed the changes.. Any idea what can cause this issue and is there a way to fix it? Thank you
I have a requirement to get all records changed after a certain date.
I have set modifiedTimestamp as Queryable, but when I attempt to do any query at all using the following operators: > < >= <= no results are returned. I have confirmed there are records that should be returned. The only operator that works is == and !=.
I have tried the following:
NSPredicate(format: "modificationDate > %@", lastFetched as NSDate)
NSPredicate(format: "___modTime > %@", lastFetched as NSDate)
I'm using NSPersistentCloudKitContainer to sync CoreData to CloudKit public database but I have the problem that canUpdateRecord always returns true even for objects created by another iCloud user with _icloud role permission set to Read only. Am I missing something?
I am working on an app that will have about 200mb of data in a database. I would like to install a json file with 3-4 mb on the phone with the app, and then be able to send small packets of information to the user of the app as they are using the app, for example if they hit a button with a link to an instagram page, I can have the database/server send the link to the app, it will be just small bits of information going from the database to the phone, and perhaps I would do some location tracking and getting some information from the user to build up personal preferences etc. I'm wondering if coredata would be the best way to get smaller packets of information to the phone quickly or if I should think about firebase or something else. I was interested in using Cloudkit, but it seems like Cloudkit is really more about syncing devices and for getting information from the user to the database. It's been really hard to find any information about the strengths versus weaknesses of different databases and how they interact with mobile apps.
I have enabled an App Group in my App and the Widget Extension. I use it to share my UserDefaults. Every time the app starts I now get the following error message in the Xcode console:
Couldn't read values in CFPrefsPlistSource<0x303034510> (Domain: group.XX.XXXX.XXXX, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
The shared UserDefaults itself works without problems.
Any ideas how I could get rid of this warning?
Juust before I initiate an App Transfer...
We have sandboxed 'mobile' versions of our app (iOS and mac App) to transfer to the surviving company.
However, we also have a 'full' non-sandboxed legacy desktop dmg version of the app, available for Mac (and Win). This has access to the same iCloud folder
So the question is, what happens to the iCloud app folder on the Mac if they are only using this desktop version, once the transfer takes place? Will it remain visible on the Mac, and will it remain accessible by the desktop version if so?
I expect that although the iCloud entitlement is transferred, as it is another Team ID, the legacy app will not be able to read/write without user prompted permission. What I hope at the least, is that the folder doesn't become invisible on that machine...
I'm seeing a lot of these in my logs:
PersistentIdentifier PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-swiftdata://Course/BC9CF99A-DE6A-46F1-A18D-8034255A56D8), implementation: SwiftData.PersistentIdentifierImplementation) was remapped to a temporary identifier during save: PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(url: x-coredata:///Course/t58C849CD-D895-4773-BF53-3F63CF48935B210), implementation: SwiftData.PersistentIdentifierImplementation). This is a fatal logic error in DefaultStore
... though everything seems to work.
Does anyone know what this means in this context? Anything I can do to not have this appear?
Hello,
After iOS 18 fetchChanges() method of CloudKitSync engine does not work as before. Calling the function doesn't fetch changes always, but it does fetch on iOS 17.
However going background and foreground again fetches changes automatically.
I'm a bit lost because of a problem I never experienced before: I create entries in 3 Core Data tables and link them. As long as the app is open, everything is fine, I can see the database entries in the three tables. Once the App is closed and restarted, however, the new entries in two of the three tables are gone.
I use Core Data for data storage and DB Browser for SQLite for inspecting the database running in the Simulator.
Here's the relevant function where all Core Data handling happens:
/**
Creates a new ComposedFoodItem from the ComposedFoodItemViewModel.
Creates the related FoodItem and the Ingredients.
Creates all relationships.
- Parameter composedFoodItemVM: The source view model.
- Returns: A Core Data ComposedFoodItem; nil if there are no Ingredients.
*/
static func create(from composedFoodItemVM: ComposedFoodItemViewModel, generateTypicalAmounts: Bool) -> ComposedFoodItem? {
debugPrint(AppDelegate.persistentContainer.persistentStoreDescriptions) // The location of the .sqlite file
let moc = AppDelegate.viewContext
// Create new ComposedFoodItem (1)
let cdComposedFoodItem = ComposedFoodItem(context: moc)
// No existing composed food item, therefore create a new UUID
cdComposedFoodItem.id = UUID()
// Fill data
cdComposedFoodItem.amount = Int64(composedFoodItemVM.amount)
cdComposedFoodItem.numberOfPortions = Int16(composedFoodItemVM.numberOfPortions)
// Create the related FoodItem (2)
let cdFoodItem = FoodItem.create(from: composedFoodItemVM, generateTypicalAmounts: generateTypicalAmounts)
// Relate both (3)
cdComposedFoodItem.foodItem = cdFoodItem
// Add cdComposedFoodItem to composedFoodItemVM
composedFoodItemVM.cdComposedFoodItem = cdComposedFoodItem
// Add new ingredients (4)
if let cdIngredients = Ingredient.create(from: composedFoodItemVM) {
cdComposedFoodItem.addToIngredients(NSSet(array: cdIngredients))
// Save new composed food item
try? moc.save()
// Return the ComposedFoodItem
return cdComposedFoodItem
} else {
// There are no ingredients, therefore we delete it again and return nil
moc.delete(cdComposedFoodItem)
try? moc.save()
return nil
}
}
What the function does:
Creates a new entry in table ComposedFoodItem
Creates another new entry in another table FoodItem
Relates both entries
Creates another 1..n entries in a third table Ingredient and links these to the entry created in step 1
All this works fine, I can see all relations and entries in the database.
Then I quit and restart the app. The entry created in step 2 is still there, but the entries created in steps 1 and 4 are gone, as well as the relationships (of course).
My suspicion: I recently implemented a Core Data migration from Data Model version 1 ("EasyFPU") to version 2 ("EasyFPU 2"). In this migration, I have two custom migration policies for exactly the two tables, which are not stored. The migration policies are pretty simple (and identical for both tables):
/**
No Ingredient is created in the destination model, i.e., there will be no Ingredients
*/
override func createDestinationInstances(forSource sourceIngredient: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// Do nothing on purpose
debugPrint("Not migrating Ingredient with ID: \((sourceIngredient as? Ingredient)?.id.uuidString ?? "unknown")")
}
And what I suspect is, that this migration policies are somehow called when restarting the app, but I have no idea why, because the migration has already happened before. If I set a breakpoint in the debugPrint line of the code snippet above, I actually never reach this breakpoint - as expected. Nevertheless are the two tables Ingredient and ComposedFoodItem empty after restart.
My AppDelegate Core Data persistentContainer variable looks like this:
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "EasyFPU")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
I tried to replace "EasyFPU" with "EasyFPU 2", but this apparently is not the version, but the container name.
Any idea? Thanks in advance!
I'm building a macOS + iOS SwiftUI app using Xcode 14.1b3 on a Mac running macOS 13.b11. The app uses Core Data + CloudKit.
With development builds, CloudKit integration works on the Mac app and the iOS app. Existing records are fetched from iCloud, and new records are uploaded to iCloud. Everybody's happy.
With TestFlight builds, the iOS app has no problems. But CloudKit integration isn't working in the Mac app at all. No existing records are fetched, no new records are uploaded.
In the Console, I see this message:
error: CoreData+CloudKit: Failed to set up CloudKit integration for store: <NSSQLCore: 0x1324079e0> (URL: <local file url>)
Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.cloudd was invalidated: failed at lookup with error 159 - Sandbox restriction." UserInfo={NSDebugDescription=The connection to service named com.apple.cloudd was invalidated: failed at lookup with error 159 - Sandbox restriction.}
I thought it might be that I was missing the com.apple.security.network.client entitlement, but adding that didn't help.
Any suggestions what I might be missing? (It's my first sandboxed Mac app, so it might be really obvious to anyone but me.)
I’m developing an app for inspections that allows users to develop their own template for inspections. Because of this, my data structure has become more than a little complex (see below).
This structure does allow a great deal in flexibility, through, as users can add groups, rows, and individual fields as needed. For example, users can add a header group, then a row to the header with fields for the Building Number, Unit Number, & Inspection Date.
However, I don’t have an efficient way to sort the inspections by the values in these fields. SwiftData sorting is keypath based, which won’t allow me to sort the inspections based on the values only in fields with specific labels.
As an alternative, I can query for fields with a specific label and do a compactMap to the inspection. But this doesn’t scale well when working with potentially hundreds or thousands of inspections as compactMap takes a lot longer then the query takes. It also doesn’t work well if I want to filter inspections or sort using values in multiple user defined fields.
Models below are greatly simplified to just get the point across. More than happy to provide some additional code if asked when I’m back at my laptop with the source code. (Typing this on my iPad at the moment)
@Model final class Inspection {
// init and other fields
var groups: [Group]?
}
@Model final class Group {
// init and other fields
@Relationship(inverse: \Inspection.groups)
var inspection: Inspection?
var rows: [Row]?
}
@Model final class Row {
// init and other fields
@Relationship(inverse: \Group.rows)
var group: Group?
var fields: [Field]?
}
@Model final class Field {
// init and other fields
var label: String?
var type: FieldType // enum, denoting what type of data this is storing
var stringValue: String?
var boolValue: Bool?
var dateValue: Date?
@Attribute(.externalStorage) var dataValue: Data?
@Relationship(inverse: \Row.fields)
var row: Row?
}
I'm studying sharing through this link. I followed the first steps by changing the bundle identifier of the project, the tests and placing my own container in the config and in the info.plist.
https://github.com/apple/sample-cloudkit-zonesharing
The app appears and in the log it appears that it has managed to access my iCloud, but when I click on share and share something, the following message appears in the console, on the simulator and on the iPhone:
"No options were found, providing default value for access type"
"No options were found, providing default values for permissions"
"connection invalidated"
And finally, when I click on the shared link, the following message appears:
"Item unavailable
The owner stopped sharing, or you don't have permission to open it."