Why Can't I Use Subscripting on a Ckrecord Object in Swift

Stack overflow when defining subscript on CKRecord in Swift

After some testing and debugging (via a subclass), I discovered that, for CKRecord, objectForKey: does indeed call objectForKeyedSubscript:. Also, implementing subscript in a Swift class that is marked @objc implicitly (by descending from NSObject) or explicitly means that subscript is implemented as objectForKeyedSubscript:.

This means that implementing subscript on CKRecord in an extension hides the default implementation, which causes the stack overflow.

Does subscript operator work for CKRecord?

On this post you can see an explanation why subscripting CKRecord does not work and why you also can not add your own.

Stack overflow when defining subscript on CKRecord in Swift

There is however a sample of a workaround that comes close.

How to connect CKQueryNotification to a CKRecord or CKSubsription?

if you receive a push notification, then you should see if you can cast it to a CKQueryNotification. Indeed you should call -[CKDatabase fetchRecordWithID:completionHandler:] for getting the complete record. you can then use the .recordType to see what kind of record it is.

You only have a problem when you have multiple subscriptions for the same recordType. You could solve that by checking if the object complies to the predicate that you used for that subscription. See the predicate.evaluateWithObject method for this. You can not use this if you have a predicate for a CKReference.

If you want a working sample of this, then you could have a look at:
https://github.com/evermeer/EVCloudKitDao
Which has some other nice features like automatic parsing from and to CKRecord and caching on device of results.

How to save a dictionary (or any other complex structure) into iCloud using Cloudkit?

Although CKRecord does have the ability to support an Array, it is primarily for storing simple data types like strings, numbers, and CKRecord.Reference. You don't specifically call out what your 'value' type is, but here is an example of using JSONEncoder/JSONDecoder to add support for writing/reading any codable type to a CKRecord. The encoder/decoder is simply converting the Encodable/Decodable type to/from a binary Data representation, which CKRecord also supports.

private let encoder: JSONEncoder = .init()
private let decoder: JSONDecoder = .init()

extension CKRecord {
func decode<T>(forKey key: FieldKey) throws -> T where T: Decodable {
guard let data = self[key] as? Data else {
throw CocoaError(.coderValueNotFound)
}

return try decoder.decode(T.self, from: data)
}

func encode<T>(_ encodable: T, forKey key: FieldKey) throws where T: Encodable {
self[key] = try encoder.encode(collection)
}
}

Usage would look like the following:

let collection: [[Date: String]] = [[:]]
let record = CKRecord(recordType: "MyRecord")
try? record.encode(collection, forKey: "collection")
let persisted = try? record.decode(forKey: "collection") as [[Date: String]]

Check if record subscription already exists in CloudKit

You can use fetchAllSubscriptionsWithCompletionHandler to query all existing subs, then you can check subscriptionID property on each sub returned in the completion handler. The objective-c version looks like:

    [publicDatabase fetchAllSubscriptionsWithCompletionHandler:^(NSArray<CKSubscription *> * _Nullable subscriptions, NSError * _Nullable error)
{
NSMutableArray *subIDs = [NSMutableArray new];
for (CKSubscription *sub in subscriptions)
{
if ([sub.subscriptionID isEqualToString:@"whatever"];
{
//do some stuff
}
}
}];

However, you mention re-entering a view controller and re-running this check. Not that this check issues a request to the server each time and will thus count against your transaction and transfer quotas. I recommend instead that you run this check once on app startup, and then save the state of each sub in class variables so you don't have to keep re-querying the server repeatedly with unnecessary calls.

CloudKit notifications

Just fill the .alertLocalizationArgs of the CKNotificationInfo object

The documentation for the .alertLocalizationArgs says:

Use of this property is optional. This property contains an array of
NSString objects, each of which corresponds to a field of the record
that triggered the push notification. Those names are used to retrieve
the corresponding values from the record. The values are then used to
replace any substitution variables in either the alertBody or
alertLocalizationKey strings. The values themselves must be NSString,
NSNumber, or NSDate objects. Do not specify keys with other values.
String values that are greater than 100 characters in length may be
truncated when added to the push notification.

If you use %@ for your substitution variables, those variables are
replaced by walking the array in order. If you use variables of the
form %n$@, where n is an integer, n represents the index (starting at
1) of the item in the array to use. Thus, the first item in the array
replaces the variable %1$@, the second item replaces the variable
%2$@, and so on. You can use indexed substitution variables to change
the order of items in the resulting string, which might be necessary
when you localize your app’s messages.

Here are 2 samples of how I use it:

            notificationInfo.alertBody = "%1$@ : %2$@"
notificationInfo.alertLocalizationArgs = ["FromName", "Text"]

And the 2nd sample:

            notificationInfo.alertLocalizationKey = "News: %1$@"
notificationInfo.alertLocalizationArgs = ["Subject"]

Where you have the "News: %1$@" as a key in the Localizable.strings file

Update: It looks like you now require to use the .alertLocalizationKey. So the first sample does not work anymore.



Related Topics



Leave a reply



Submit