Get Newest Record from CloudKit Database - ios

I'm new to CloudKit (and haven't used NSPredicate much) and am looking to do something which, I'd imagine, is quite basic. I'd like to retrieve the data record with the newest creationDate.
Right now I'm just pulling all the records and scooping up the last one, but there's got to be a more elegant way. Here's my current approach:
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "NewsItem", predicate: predicate)
CKContainer(identifier: "…").publicCloudDatabase.perform(query, inZoneWith: CKRecordZone.default().zoneID) { result, error in
let sortedRecords = result.sorted {
guard let date1 = $0.value(forKey: "creationDate") as? Date,
let date2 = $1.value(forKey: "creationDate") as? Date else {
return false
}
return date1 < date2
}
let item = NewsItem(from: sortedRecords[0])
…
}
I've used NSPredicate to get a subset of results before, but never max/min style results. Is this possible?

This seems like a step in the right direction:
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "NewsItem", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
CKContainer(identifier: "…").publicCloudDatabase.perform(query, inZoneWith: CKRecordZone.default().zoneID) { result, error in
let item = NewsItem(from: result.last)
…
}
Though I'm still curious if there's a way to simply query by max creationDate…

Related

CKQueryOperation returning no results when sort descriptors are used

I am working on a CloudKit based project where it would be very helpful to use sort descriptors to get the most recent results from the database.
func getConversationPosts(for targetConversation: MessageConversation, completionHandler: #escaping ([MessagePost]) -> Void) {
var post = MessagePost()
let getRecordsOperation = CKQueryOperation()
getRecordsOperation.qualityOfService = .userInteractive
getRecordsOperation.resultsLimit = 1
getRecordsOperation.query = CKQuery(recordType: "MessagePost", predicate: NSPredicate(format: "conversationName = %#", targetConversation.conversationID.recordName))
getRecordsOperation.query?.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] // If you comment this out results are returned
/*getPostsOperation.queryCompletionBlock = {(searchCursor, error) in
print("sQ: \(searchCursor)")
}*/
getRecordsOperation.recordFetchedBlock = {(postRecord) in
let newPost = MessagePost()
newPost.textContent = postRecord.value(forKey: "textContent") as! String
newPost.poster = User(userName: postRecord.value(forKey: "postUser") as! String)
// posts.append(newPost)
post = newPost
}
getRecordsOperation.completionBlock = {() in
completionHandler(post)
}
OperationQueue().addOperation(getRecordsOperation)
}
The above code always works if the line that adds the sortDescriptors is removed. Even without a result limit, if any sort descriptor is added, the recordFetchedBlock isn't even called, but the completion block is. What could be causing this?

Querying Core data with predicates

So i am building this app using CoreData.
The two entities I have are Lists and Items. They have a to many relationship i.e. A List can have multiple items.
For example: List1 has Items: item1, item2
I have written the code for storing the Items in the specific list but i am having a difficult time on figuring out how to fetch and proccess the Items from a specific List.
What I have done so far is as follows
func getItemsOnList(){
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
//fetchRequest to get the List
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "List")
let predicate = NSPredicate(format: "title == %#", listName)
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = predicate
if let fetchResults = try? context.fetch(fetchRequest){
if fetchResults.count > 0 {
for listEntity in fetchResults {
let list = listEntity as! List
print(list.title as Any)
itemsOnList = list.contains!
print(itemsOnList)
print("The list with name:\(list.title)has \(itemsOnList.count) items")
}
}
}
}
This function returns an NSSet which is suppose to contain all the Items in that particular List.
My Data model is :
My questions are:
A. Is the way I coded the getItemsOnList() function correct? Or is there something I am doing wrong.
B. Given that the code is correct and the NSSet I get is correct with all the Items, how would I get each Item in that NSSet in order for me to put it on a TableView.
func getItemsWithFilter(filterQuery:NSPredicate,sortBy:String?,order:Bool) -> Array<Items> {
var fetchedResults:Array<Items> = Array<Items>()
let fetchRequest = NSFetchRequest(entityName: "Items")
fetchRequest.predicate = filterQuery
if sortBy != nil{
let sortDescriptor = NSSortDescriptor(key:sortBy! ,
ascending:order )
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = sortDescriptors
}
//Execute Fetch request you can go with your approach to
do {
fetchedResults = try self.mainContextInstance.executeFetchRequest(fetchRequest) as! [Items]
} catch let fetchError as NSError {
print("retrieveById error: \(fetchError.localizedDescription)")
fetchedResults = Array<Items>()
}catch {
fetchedResults = Array<Items>()
}
return fetchedResults
}
for calling this method you can pass the List item in predicate to as query saying fetch Items in which List.id == XXX
let predicate = NSPredicate(format: "ANY list.name in %#", name)
let myResult = self.getItemsWithFilter(predicate,sortBy:nil,order:false)
Answers:
A) Yes. You are using the graph of objects from a fetch. That is the main functionality of Core Data.
B) To fill a table view you cannot use a set. You need some kind of sorted list of elements. That is, an array. Use -orderedArrayUsingDescriptors: to get the sorted array.

How to query CloudKit for recordID IN [CKRecordID]

My predicate wants to exclude some records that are already downloaded and available in a [CKRecordID]. Now I can query 1 CKRecordID[0], but not the [CKRecordID] array. How can I query the array?
let excludeIDs: [CKRecordID]
This works:
let pred1 = NSPredicate(format: "NOT(recordID = %#)", excludeIDs[0])
But this doesn't:
let pred1 = NSPredicate(format: "NOT(recordID IN %#)", excludeIDs)
ERROR:
loadImageCompareRecordIDsAndEndDateThatHaveNotEnded Error: Invalid predicate: Invalid predicate: Array members must conform to CKRecordValue: (
"",
"",
"",
"",
""
) (CKRecordID)
The other general parts of the code:
let sort = NSSortDescriptor(key: "creationDate", ascending: false)
let query = CKQuery(recordType: MyRecordTypes.ImageCompare, predicate: pred1)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["endDate"]
operation.resultsLimit = 50
Using [CKReference] and not [CKRecordID] solved it.
To be explicit (because it took me hours to get this right)...
let refs = excludeIDs.map { CKRecord.Reference(recordID: $0.recordID, action: .none) }
let pred1 = NSPredicate(format: "NOT(recordID IN %#)", refs)

Adding to a NSCompoundPredicate

I want to make my code more reusable for core data, I have a fetchRequest with predicates that I always must use. However some methods require more conditions. I want to add those conditions to the predicate list however I am unsure how to do this. I would like a method to return a predicate with basic queries and then add on to those queries.
let fetchRequest = NSFetchRequest(entityName: "Stop")
var currentTime = NSDate.getTime()
var sort = NSSortDescriptor(key: "time", ascending: true) // sort by bus stop
fetchRequest.sortDescriptors = [sort]
let predicate = NSPredicate(format: "time >= %ld", currentTime)
let predicate2 = NSPredicate(format: "stop_name == %#", stop)
let predicate3 = NSPredicate(format: "busParent.direction == %#", direction)
let predicate4 = NSPredicate(format: "busParent.name == %#", name)
let predicate5 = NSPredicate(format: "busParent.schedule == %ld", schedule)
fetchRequest.predicate = NSCompoundPredicate.andPredicateWithSubpredicates(
[predicate, predicate2, predicate3, predicate4, predicate5])
// EXAMPLE: HOW WOULD I ADD TO THE COMPOUND PREDICATE ALREADY MADE?
fetchRequest.predicate.????
You can compound NSCompoundPredicates with other NSPredicates:
let currentPredicate = fetchRequest.predicate!
let additionalPredicate = NSPredicate(format: "whatever",...)
let nextPredicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: [currentPredicate, additionalPredicate])
fetchRequest.predicate = nextPredicate
By specifying .AndPredicateType you will "narrow down" the existing results to those that also meet the additional predicate. But you could also specify OrPredicateType or NotPredicateType to add to or exclude (respectively) the existing results.

Core Data NSFetchedResultsController one to many for swift titleForHeader

I have found many examples of this, but none seem to work for my situation.
My data model is this.
I have a one to many relationship of Project <->> Entry.
Entry has an attribute of category which is a String.
I want to populate a TableView inside a Project that shows all the entries for that Project organized by the Entry.category as the titleForHeaderInSection.
func allCategoriesFetchRequest() -> NSFetchRequest {
var fetchRequest = NSFetchRequest(entityName: "Project")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = NSPredicate(format:"(ANY entries == %#)", self.currentProject)
return fetchRequest
}
And loading the data....
func loadInitialData() {
fetchedResultsController = NSFetchedResultsController(fetchRequest: allCategoriesFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: "entries.category", cacheName: nil)
fetchedResultsController?.delegate = self
fetchedResultsController?.performFetch(nil)
}
I get no error, but I get no data either. I am a beginner with iOS and have been struggling with this for a couple of days now.
Any help is greatly appreciated.
Well, there is a lot here.
Your current fetch request says "find any project where its entries are equal to the current project". When what you want to say is "find any entry where its project is equal to the current project".
Lets try to do it using an NSFetchRequest.
var fetchRequest = NSFetchRequest(entityName: "Entry")
let sortDescriptor = NSSortDescriptor.sortDescriptorWithKey("name", ascending: true)
let predicate = NSPredicate(format: "project == %#", self.currentProject)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = predicate
The reason your code doesn't error is because its valid code. It will look for all the projects where an entry is equal to the current project, which will be never, because you won't have any projects as entry objects in your entries field of a project. So that is why you are getting no data.
If you don't want to use a NSFetchedResultsController, and if your model is set such that you have a set of Entry objects in the entries field of your Project object, then you can just do:
let entries = self.currentProject.entries
to get all your Entry objects. This will be a NSSet though, so you have to change it into an array to have it sorted
let sortDescriptor = NSSortDescriptor.sortDescriptorWithKey("name", ascending: true)
let sortedEntries = entries.sortedArrayUsingDescriptors([sortDesriptor])
Wrote all this code without testing, so let me know if there is a problem and I will update.
Excellent. Thank you. Just what I was looking for. This is what I tweaked based on ColdLogic's answer. In case it helps someone else.
var fetchRequest = NSFetchRequest(entityName: "Entry")
let sortDescriptor = NSSortDescriptor(key: "category", ascending: true)
let predicate = NSPredicate(format: "project == %#", self.currentProject)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.predicate = predicate

Resources