How to query CloudKit by lastModifiedUserRecordID? - swift

Based on Apple documentation, lastModifiedUserRecordID, is a system field that's auto generated on iCloud.
I want to fetch records from the public iCloud database that where last modified by me.
I set "modifiedBy" as Queryable on the CloudKit Dashboard and added this predicate:
let userId = CKRecord.Reference(recordID: userCKRecordID, action: .none)
lastModifiedPred = NSPredicate(format: "lastModifiedUserRecordID = %#", userId)
And it returns no results. If I remove this predicate, it returns 1 result and I can see in the dashboard that the last modified by record id (recordName) matches with the user recordName I'm sending it.
Oddly enough, using "creatorUserRecordID" and passing the same value as above does return proper results but I need lastModifiedBy.
EDIT: Per the suggestions I tried to use the actual userCKRecord.recordName as a string and sending this predicate to CloudKit.
lastModifiedPred = NSPredicate(format: "lastModifiedUserRecordID = %#", userCKRecord.recordName)
and received this error:
"Field \'___modifiedBy\' has a value type of REFERENCE and cannot be
queried using filter value type STRING"
As I mentioned above, I can query creatorUserRecordID just fine using the original predicate at the top (using CKRecord.ID), but it just doesn't work for lastModifiedUserRecordID.

Since you want to fetch records that were last modified by you, you have to get first your UserRecordID. This is done by calling
fetchUserRecordID(completionHandler: { (myUserRecordID, error) in
… // see below
}
see the docs. This way, you get your user myUserRecordID.
With this, you can setup your query predicate:
let lastModifiedPred = NSPredicate(format: "lastModifiedUserRecordID = %#", myUserRecordID)
and execute your query.

You are using NSPredicate to compare a string literal. Where userId is not a string literal. Use this.
lastModifiedPred = NSPredicate(format: "lastModifiedUserRecordID = %d", INT-VALUE-OF-userId)

Based on apple documentation on https://developer.apple.com/documentation/cloudkit/ckrecord/id
CKRecord.ID -> class ID : NSObject
A record ID object consists of a name string and a zone ID. The name
string is an ASCII string not exceeding 255 characters in length.
comparing an object and string won't give you the result, try to cast the CKRecord.ID as String maybe help

Related

Query iCloud public database to find records with properties that contain the search string

I want to use the iCloud public database to provide my users with items of a certain RecordType "Item":
Item: {
"name": String,
"image": Asset // this is a PNG or JPEG
}
My View contains a TextField where the user can type in a searchText. Whenever the searchText changes, I want to query the iCloud public database for items whose names contain this searchtText.
The code would look something like this:
import SwiftUI
import CoreData
import Foundation
import CloudKit
struct ContentView: View {
#State var searchText: String = ""
func fetchCloudData() {
let container = CKContainer.default()
let publicDB = container.publicCloudDatabase
let predicate = NSPredicate(value: true)
let predicate = NSPredicate(format: "name contains %#", searchText)
publicDB.perform(query, inZoneWith: nil) { (records, error) in
guard error == nil else {
print("Error fetching records: \(error!)")
return
}
if let records = records {
records.forEach({ (record) in
print(record)
})
}
}
}
var body: some View {
TextField("Type in name...", text: $searchText)
Button(action: {
fetchCloudData()
}, label: {
Text("Fetch all items")
})
}
}
The problem is that the operator contain only works on lists and not on strings like the "name" property. There are the operators BEGINSWITH and =, but they only return those items whose names either begin with or equal the searchText. For example, if the user types in "anan", I want to get an Item with the name "Banana" be returned as well. Is there a way to do this or do I have to resort to downloading every single item at the launch of the app and filter them locally on the device?
Remark
There is already a similar question: How do I perform text search on Cloudkit records?
The suggested answer was to use
NSPredicate(format: #"self contains %#", searchText)
For Swift, the # sign has to be removed so the query works. However, this only queries the records for properties which exactly match the searchText. This does not work if only part of the name matches the searchText.
Apple's documentation clearly states:
With one exception, the CONTAINS operator is only for testing list membership. The exception is when you use it to perform full-text searches in conjunction with the self key path. The self key path causes the server to look in searchable string-based fields for the specified token string. For example, a predicate string of #"self contains 'blue'" searches for the word blue in all fields that you mark for inclusion in full-text searches. You can’t use the self key path to search in fields with a type that isn’t a string.
Possibly this question will help you with a full-text search: How to do a CloudKit full text search that includes a token search

URI typed attributes in CoreData: how to query with NSPredicate

I have a CoreData-Entity that stores an attribute called "imageUrl" of type "URI".
It's used to store URL's (as in Swift URL / NSURL) for eg. rendering remote images.
How would I query for the string representation of an URI-type attribute?
Example: I'd like to get all objects that match "http://mydomain.jpg" or URL(string: "http://mydomain.jpg") to be more precise.
For attributes that are "String"-typed this would be sufficient:
NSPredicate(format: "myStringAttribute LIKE %#", "http://mydomain.jpg")
Would the following work for URI-type attributes?
NSPredicate(format: "imageUrl LIKE %#", URL(string: "http://mydomain.jpg"))
I'm answering to not let that question without an answer, but in my opinion, we three #nylki (the author), #Joakim Danielson and myself answered it together. I'll mark it as "Community Wiki" then.
URI in CoreData is a for URL objects. It's written as such in the documentation of NSAttributeDescription.AttributeType for the NSAttributeDescription.AttributeType.uri.
LIKE keyword in the predicate is for String comparison as stated by the Predicate Format String Syntax documentation, so we need to use = instead.
So the answer is:
NSPredicate(format: "imageUrl = %#", imageUrl as CVarArg)
Or
NSPredicate(format: "imageUrl = %#", argumentArray: [imageUrl])
if we don't want to use the as CVarArg.
A better way to avoid typo would be to use %K placeholder which is for %K is a var arg substitution for a key path.. Combined to #keyPath():
NSPredicate(format: "%K = %#", argumentArray: [#keyPath(YourEntityClass.imageUrl), imageUrl])
That way, if we wrote by error "imageUrI = %#" with an uppercase i instead of l that with might not see, we ensure the path.

core data predicate format in string swift4

I am using core data. For fetching data I want to give predicate as string. I have stored predicate in string attribute in database.
Below is code I am trying
let request = NSFetchRequest<Tasks>(entityName: "Tasks")
print("Query is .." ,item.query!)
request.predicate = NSPredicate(format: item.myqueryString!)
myqueryString is string attribute in core data where I am storing predicate as -
projectName == "spot"
But on fetching data it is giving error "NSInvalidArgumentException', reason: 'keypath spot not found in entity".
Anyone can explain what is wrong? Thanks in advance

Filter to NSFetchRequest

hi i'm not really understanding how the fetch filter works can anyone help me please? So i currently have this as my fetchall function which displays all of my items within my entity
im having trouble of filtering only one attribute which is a boolean. I want it to only display attributes that are true.
thankyou!
func fetchAllItems(){
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "AllItems")
do{
let result = try managedObjectContext.fetch(request)
beastList = result as! [AllItems]
} catch { print("\(error)")
}
}
Code:
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "AllItems")
request.predicate = NSPredicate(format: "something = %#", argumentArray: [true])
Note: Replace something with your boolean field name
Better way to create request
let request : NSFetchRequest<AllItems> = AllItems.fetchRequest()
Naming convention:
It is better to name your entity in singular form, so that each row in your entity could be in singular form.
AllItems seems very generic, if you are storing cars, the entity name could be Car. The variable that stores the result of the fetch request could be cars.
Reference:
https://developer.apple.com/documentation/foundation/nspredicate
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html

Retrieving Data from FireBase SWIFT

I'm trying to get data out of a firebase database if the addedByUser = a value stored in the application.
With what I currently have, nothing is appearing in the table.
The firebase database looks like this:
workout1
"name"
"addedByUser"
My code looks like this:
workout3Ref.observeEventType(.Value, withBlock: { snapshot in
var newWorkout = [Workout1Info]()
for item in snapshot.children {
let createdBy = snapshot.value["addedByUser"] as? String
if createdBy == Username.sharedInstance.userDetail {
let workoutItem = Workout1Info(snapshot: item as! FDataSnapshot)
newWorkout.append(workoutItem)
}
}
self.workouts = newWorkout
self.tableView.reloadData()
})
When I get data normally without comparing if the addedByUser it normally works. Does anyone know why it isn't working?
Thanks for the help
Here's my guess based on a super vague question with some coding issues:
The createdBy is the users name or uid as a string.
The Username.sharedInstance.userDetail is an object (of some kind)
So you are comparing a String to an object that isn't a string, and that won't work.
Also, if the node you are query-ing is the /workout1 node, then you should probably name your reference workout1Ref instead of workout3Ref. That may help keep things straight in code.
While you are comaparing String createdBy with Username.sharedInstance.userDetail check your property Username.sharedInstance.userDetail also should be of String type.