CloudKit - How to retrieve the ckRecordID of the record just saved using CKModifyRecordsOperation - cloudkit

I'm using CoreData for keeping a local cache of records in CloudKit. When saving a new record, I do the following:
Insert record to CoreData. I flag this record as not updated in CloudKit. Just in case my CKModifyRecordsOperation fails, I can still update it at a later time to CloudKit using this flag.
Insert record to CloudKit using CKModifyRecordsOperation.
Try fetching the ckRecordID of the record inserted in step #2. (That's where my logic fails as I'm not sure how I can achieve this). I do not have any other keys (reference) and wish to use only CKRecordID as a reference between CoreData and CloudKit.
Update the ckRecordID (fetched in step #3) to CoreData.
What would be the best logic to solve the above? Thank you for your time and responses.

I solved this by creating a CKRecordID locally and updating it in CloudKit. Below is the quote from apple documentation:
To assign a custom record ID to a new record, you must create the
CKRecordID object first. You need to know the intended name and zone
information for that record, which might also require creating a
CKRecordZone.ID object. After creating the record ID object,
initialize your new record using its init(__recordType:recordID:)
method.
Here's my code:
let zone = CKRecordZone(zoneName: Schema.Zone.group)
let uuid = NSUUID()
let recordType = Schema.RecordType.group
let recordName = uuid.uuidString
let recordID = CKRecordID(recordName: recordName, zoneID: zone.zoneID)
let newRecord = CKRecord(recordType: recordType, recordID: recordID)

Related

Filter NSArraycontroller Core Data before searching

I have an NSArrayController managing contacts stored in Core Data and presented in a NSTable. The table provides a search field to the user to look for contacts by name. Everything is implemented in Swift using Cocoa bindings and works well.
Now I want to add an additional filter before the user types his search string. The filter should limit the contacts via an additional attribute like a creation date. How can this be done? Via modifying the predicate or do I need to subclass NSArrayController?
Any help would be appreciated.
Meanwhile I tried the approach mentioned by Willeke. Setting a default fetch predicate for the array controller initially with the Interface Builder and executing the following code every time the date date value changes, worked fine.
let fetchRequest = ContactArrayController.defaultFetchRequest()
fetchRequest.predicate = NSPredicate(format: "events.#max.eventDate < %#", lastDate as NSDate)
try? ContactArrayController.fetch(with: fetchRequest, merge: false)
where event is a related entity to the entity in the array controller.
However this appears to be an overhead to me, as the data needs to be fetched every time into the array controller instead of just applying a filter
Maybe there are better solutions

How to get a fresh copy of CKRecord from NSPersistentCloudKitContainer?

According to Apple docs here
https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/reading_cloudkit_records_for_core_data
You can get a CKRecord from a NSManagedObject using NSPersistentCloudKitContainer.
Access CloudKit Objects
You can access a managed object’s CKRecord directly through its associated context using record(for:) for a single record, or records(for:) for multiple records. To retrieve the record ID only, use recordID(for:), or recordIDs(for:).
Alternatively, use the class functions record(for:), records(for:), recordID(for:), and recordIDs(for:) on NSPersistentCloudKitContainer.
But it doesn't say how to get a fresh copy? If you are working with CloudKit Sharing, you are modifying CK without going through CoreData. In which case you have to wait for NSPersistentCloudKitContainer to next fetch for updates.
But if you want to present the latest Sharing Status, you need to query Cloudkit. How to do this?
Using the fetch with RecordID (below) will let you get the latest record:
func fetch(withRecordID recordID: CKRecord.ID,
completionHandler: #escaping (CKRecord?, Error?) -> Void)
If you want to fetch it and then update a property and save, you'd do something like:
Get the record from cloudkit
CKContainer.default().publicCloudDatabase.fetch(withRecordID: user.id!) { updatedRecord, error in
Then set the new array for the key and key type I'm saving (where array for user is the thing I've updated and 'keyType' is the CK record field name)
updatedRecord!.setObject(arrayForUser as __CKRecordObjCValue?, forKey: keyType)
Then make the save call, passing the updated record:
CKContainer.default().publicCloudDatabase.save(updatedRecord!) { savedRecord, error in

How to add fields to a Realm model class

My class has only 4 fields:
class List: Object {
dynamic var name = ""
dynamic var date = ""
dynamic var notes = ""
dynamic var info = ""
}
And I want to add 2 more:
dynamic var website = ""
dynamic var telephone = ""
If I add these fields to the class then Realm gives me an error due to the new fields. How can I update this class while saving all user data?
There are two alternatives to consider:
If your app is under development and has not been released yet, then you can delete the installed app and reinstall it. Realm will use the updated model class schema when recreating the database.
If your app is already released or you otherwise want to preserve the data in the Realm file, you can gracefully upgrade the existing Realm to the new schema. You can do this by performing a migration, which instructs Realm to update the schema on the file on disk to match the model classes in your application, and gives you a chance to perform any modifications you may need to the data within the Realm file to accommodate the changes in your model classes.
This happens as Realm creates an internal structure with your model. Each time you need to change your model (happens quite a lot), you need to migrate your current model to new one.
The way to do this is:
Make whatever changes you want to make to your Data Model
Add this inside your application(application:didFinishLaunchingWithOptions:) in app delegate
let config = Realm.Configuration(
schemaVersion: 1, //Increment this each time your schema changes
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
//If you need to transfer any data
//(in your case you don't right now) you will transfer here
}
})
Realm.Configuration.defaultConfiguration = config
let realm = try! Realm()
Now each time you change your schema, you simply add one to the schemaVersion. Of course migrations can be trickier, but for your model, this will do the trick.
For more information refer Realm Swift Documentation

ADO.Entity updating a single field of one record without retrieving the whole record

What is the best practise for updating a single field for one record (with specific ID) using ADO.Entity?
As far as I know, you have to retrieve the whole object by id, update the property and call SaveChanges:
int id = ...;
var db = new MyEntities();
var o = (from mo in db.myObject
where mo.id = idObject
select mo).First();
o.MyProperty = "some value";
db.SaveChanges();
But it seems a little bit overhead having to retrieve the whole object, since I don't care for the values of the record because I just want to set a property, regardless of the values.
Another option would be to create a stored procedure for this purpose...
Re: What is the best practise for updating a single field for one record (with specific ID) using ADO.Entity?
Answer: Best practice is to retrieve the entire record, update one or more fields, and then store the record. -- Just as you're doing.

Core Data primary key ID for a row in the database

Suppose I have a list of books stored in Core Data. I want to search for a book by it's primary key ID.
I know the sqlite file created by Core Data has an ID column in each table, but this doesn't seem to be exposed to me in anyway.
Does anyone have any recommendations?
-[NSManagedObject objectID] is the unique ID for an object instance in Core Data. It can be serialized via -[NSManagedObjectID URIRepresentation]. You can retrieve the objectID from a persistent store coordinator with -[NSPersistentStoreCoordinator managedObjectIDForURIRepresentation:] and then get the object from a managed object context with -[NSManagedObjectContext objectWithID:].
BUT
You should keep in mind that Core Data is not an ORM. It is an object graph management framework. That is uses SQLite (and unique row IDs) as a backend is purely an implementation detail. The sooner you can get yourself out of the SQL/RDBMS mindset, the faster you will be happy with Core Data. Instead of trying to find an object from a stored ID, consider why you need that object and what object needs it. If an instance of class Foo needs to be able to get to an instance of class Bar, why not just create an association from the Foo to the Bar and set the appropriate Bar instance as the target of the association on the appropriate Foo instance. Let Core Data keep track of object IDs.
As Barry Wark said, remember always that Core Data is not an orm. Pure SQL details are not exposed to the user and every row is just an object. By the way, sometime you should need to access the "primary key", for example when you need to sync the coredata db with external sql databases (in my case I needed it in a callback function to change the state of an object after INSERT it with success in the remote db). In this case, you can use:
objectId = [[[myCoredataObject objectID] URIRepresentation] absoluteString]
that will return a string like: x-coredata://76BA122F-0BF5-4D9D-AE3F-BD321271B004/Object/p521 that is the unique id used by Core Data to identify that object.
If you want to get back an object with that unique id:
NSManagedObject *managedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:[NSURL URLWithString:objectId]]];
NB: Remember that if the receiver has not yet been saved in the CoreData Context, the object ID is a temporary value that will change when the object is saved.
This is the way you can get the object id as String using Swift from a NSManagedObject:
entity.objectID.uriRepresentation().absoluteString
in Swift this will be done by getting ID of the row as URI then get last path of URI
entity.objectID.uriRepresentation().lastPathComponent
the output of last path will look like this
p12
this output is string so you can remove the p using:
trimmingCharacters()
// Like this
let id = entity.objectID.uriRepresentation().lastPathComponent.trimmingCharacters(in: ["p"])