Set reference value in swift for CloudKit database - swift

I'm trying to set some value for a record in my CloudKit database. I haven't problem to set any other kind of value on other fields like string or int but for Reference filedname(storyid) i get this error :
"invalid attempt to set value type STRING for field 'storyid' for type
'Storypage', defined to be: REFERENCE"
func saveToCloud(note: String)
{
let newpage = CKRecord (recordType: "Storypage")
newpage.setValue(note, forKey: "pagecontent")
newpage.setValue("UTENTE1", forKey:"username" )
newpage.setValue("84EC8E60-1467-6411-5CDC-7E85DDB51C89", forKey: "storyid")
database.save(newpage) { (record, error) in
guard record != nil else {return}
}

A reference field stores a CKReference object, not a String:
Linking to Another Record
To link records together and create a strong
relationship between them, create a new CKReference object, initialize
it with the owner record, and assign that reference object to a field
of the owned record. When you design the relationships among your own
records, make the owner the more important of two related records.
So you'll want to get the recordID of the record you're relating newpage to, then use that to create a reference object, which you can then use to set the storyid field.

Related

Convert filter expression to NSPredicate format

I would like to perform this filter method using NSPredicate, but I'm having trouble converting the syntax.
objectsCollection.filter { $0.stringIds.contains(id) }
Sample class
class Object {
let stringIds: [String]
}
let objectsCollection: [Object]
let id = "The id we want to look for"
objectsCollection.filter { $0.stringIds.contains(id) }
My attempt with NSPredicate
This is how I thought it would work, but seems like it doesn't.
let filter = NSPredicate(format: "%# IN stringIds", id)
objectsCollection.filter(filter)
Error
reason: 'Expected object of type (null) for property 'stringIds' on object of type 'Object', but received: 6011ea4dda6853a3af97376e'
There are a few issues that need to be addressed.
This
let stringIds: [String]
is not a realm object and will not be persisted in Realm.
That needs to be a List property and the object type of the list is another Realm object. Realm Lists do not support primitives (very well). Also, don't name your objects the same name as another object.
class MyClass: Object {
let myStringIdList = List<MyStringIdClass>
}
and
class MyStringIdClass: Object {
#objc dynamic var myId = ""
}
to then get all of the MyClass objects that had a certain stringId in their list
let results = realm.objects(MyClass.self).filter("ANY myStringIdList.myId == %#", idToFind)
The string inside .filter can also be an NSPredicate if needed.
You can also use LinkingObjects to navigate back to the MyClass objects as well.
One other thing, when you cast realm objects to an array, they loose their connection to realm and are no longer live updating objects.
Also, Realm objects are lazily loaded meaning that thousands of objects have very little memory impact. Casting them to an array however, loads ALL of that data and can overwhelm the device with a large data set.

Append generic element to Realm List

I'm trying to set data in a Realm database in swift via Object schema properties. Add new objects has been straightforward (and also relate and object to another), but I can't fin the way to append objects to a List property.
Let's see the snippet of code I use to create objects and add to Realm database:
func save(elements: [String : Any], realmClassName: String, realm: Realm, listObject: Object) {
// Ge properties for Realm Object subclass with name realmClassName
let properties = realm.schema[realmClassName]
// Iterate through elements array
for element in elements {
// Instantiate Realm Object through its name (realmClassName)
let thisClass: AnyClass = NSClassFromString("\(namespace).\(name)")!
let realmClass = thisClass as! Object.Type
let object = realmClass.init()
// Iterate though Object Schema properties
for property in properties?.properties as [Property]! {
object[property.name] = element[property.name]
}
// Add Object to Realm database
realm.add(object)
// Here is where I want to append object to Object List
}
}
Question is, how to do something similar to:
listObject.append(object)
Some attempt trow error like:
Could not cast value of type 'RealmSwift.List<AppName.Cars>' (0x1117446d8) to 'RealmSwift.List<RealmSwift.Object>'
Thanks.

Create Reference List Pointing to Multiple CKRecords in CloudKit

Swift 3.1, Xcode 8.3.3
I have a record type in CloudKit called Aircraft and it has a field called fieldValues that is of type Reference List.
In my code, I have an aircraft CKRecord and several fieldValue CKRecords that are children of the aircraft.
How do I put all those fieldValue records into the aircraft's reference list?
Creating a CKReference appears to only allow a single record in it:
let reference = CKReference(record: fieldValueRecord, action: .deleteSelf)
But I'm trying to connect many fieldValues to a single aircraft. Any ideas?
Swift 3.1, Xcode 8.3.3
After some trial and error, I finally figured it out. It goes something like this:
//Parent record (Aircraft)
let recordAircraft = CKRecord(recordType: "Aircraft", recordID: ...)
//Add child references (FieldValue). As far as I know, the child records must have already been created and sent to CloudKit
var fieldValueReferences = [CKReference]()
for fieldValue in fieldValues{
let ref = CKReference(record: fieldValue, action: .deleteSelf)
fieldValueReferences.append(ref)
}
//Assign the array of references to the key that represents your Reference List field
recordAircraft["fieldValues"] = fieldValueReferences as CKRecordValue
I hope that helps someone else.

Swift - Detecting whether item was inserted into NSMutableSet

This is more for interest rather than a problem, but I have an NSMutableSet, retrieved from UserDefaults and my objective is to append an item to it and then write it back. I am using an NSMutableSet because I only want unique items to be inserted.
The type of object to be inserted is a custom class, I have overrode hashCode and isEqual.
var stopSet: NSMutableSet = []
if let ud = UserDefaults.standard.object(forKey: "favStops") as? Data {
stopSet = NSKeyedUnarchiver.unarchiveObject(with: ud) as! NSMutableSet
}
stopSet.add(self.theStop!)
let outData = NSKeyedArchiver.archivedData(withRootObject: stopSet)
UserDefaults.standard.set(outData, forKey: "favStops")
NSLog("Saved to UserDefaults")
I get the set, call mySet.add(obj) and then write the set back to UserDefaults. Everything seems to work fine and (as far as I can see) there don't appear to be duplicates.
However is it possible to tell whether a call to mySet.add(obj) actually caused an item to be written to the set. mySet.add(obj) doesn't have a return value and if you use Playgrounds (rather than a project) you get in the output on the right hand side an indication of whether the set was actually changed based on the method call.
I know sets are not meant to store duplicate objects so in theory I should just trust that, but I was just wondering if the set did return a response that you could access - as opposed to just getting the length before the insert and after if I really wanted to know!
Swift has its own native type, Set, so you should use it instead of NSMutableSet.
Set's insert method actually returns a Bool indicating whether the insertion succeeded or not, which you can see in the function signature:
mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element)
The following test code showcases this behaviour:
var set = Set<Int>()
let (inserted, element) = set.insert(0)
let (again, newElement) = set.insert(0)
print(inserted,element) //true, 0
print(again,oldElement) //false,0
The second value of the tuple returns the newly inserted element in case the insertion succeeded and the oldElement otherwise. oldElement is not necessarily equal in every aspect to the element you tried to insert. (since for custom types you might define the isEqual method in a way that doesn't compare each property of the type).
You don't need to handle the return value of the insert function, there is no compiler warning if you just write insert like this:
set.insert(1)

Savings CloudKit RecordID to CoreData

I have a array of CloudKit records and want to store the record ID in to core data so when i query the cloud again i do not pull down the same records. I just do a NSpRedicate not contain the records from core data.
However, getting an error, how do I save a record ID to core data.. Think it is a Type issue.
Current Code:
coreData.recordRecordID = self.cloudKitRecords[0].recordID as? String
Current getting the error that CKRecord as String always fails, which I am not surprised. Need to be able to save the recordID then get the recordID from core data.
Thanks
You can get the id like this:
coreData.recordRecordID = self.cloudKitRecords[0].recordID.recordName
It will then be a string.
If you want to make a CKRecordID from that, then you can create one using
var myID = = CKRecordID(recordName: yourStringID)
import Foundation
import CoreData
import CloudKit
class Entity: NSManagedObject {
// MARK: - Properties
#NSManaged var recordID: CKRecordID?
// Additional properties here
}
Then change the Core Data attribute type to Transformable instead of String.