Using "timeIntervalSinceNow" in a predicate in SWIFT - ios

I am trying to query for records that are created in the last 1 hour using Cloudkit (in SWIFT)
I have tried:
let predicate = NSPredicate(predicateWithFormat : "timeIntervalSinceNow %# < %f", "creationTime", -3600)
without success. The error message refers to a parsing error.
Anyone has a clue what would be the right format?
n.b creationTime is the name of the field in the Table

Your predicate isn't valid. Take a look at Apple's guide for creating NSPredicates.
You should use a key path on the left hand side of the expression. I assume that you have an array of objects, and these objects have a property named creationTime. Then the predicate should look like this:
NSPredicate(format: "creationTime.timeIntervalSinceNow > %d", -3600)
Note that I have used "greater than" > operator instead of < in the predicate you posted. That's because a date from the past will return a negative value from timeIntervalSinceNow method, as documentation states.
EDIT:
Seems you can't use key paths in CloudKit predicates. But I think you can create a reference date (an hour ago) and use it for comparison predicate:
let now = NSDate()
if let anHourAgo = NSCalendar.currentCalendar().dateByAddingUnit(
.HourCalendarUnit,
value: -1,
toDate: now,
options: NSCalendarOptions(0)) {
let predicate = NSPredicate(format: "creationDate > %#", anHourAgo)
let filtered = records.filteredArrayUsingPredicate(predicate!)
}

Related

NSPredicate: search for an array of values in relation, NOT

I'm a bit lost, and I hope you can help me.
I have two arrays
let stars = ["Paul", "Ringo"]
let visitors = ["bob", "mary"]
Then I have Core Data entities Rockstar and Person. And a one-to-many relation fans between the two.
Now I want to find a couple of specific Rockstars, and make sure that they don't have visitors as fans.
I try to do that with a compound predicate, roughly like this:
let starsPredicate = NSPredicate(format: "id IN %#", stars)
let fansPredicate = NSPredicate(format: "NOT (fans.personid CONTAINS %#)", visitors)
and finally
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [starsPredicate, fansPredicate])
I'm afraid this results in two questions:
What is the correct syntax for the fansPredicate? It works fine with one value, but it crashes on an array
Is this possible with a compound predicate at all? I think if the Person entity is empty, I get zero records from the compoundPredicate.
This can be achieved with a “SUBQUERY”:
let fansPredicate = NSPredicate(format: "SUBQUERY(fans, $f, $f.personid IN %#).#count = 0",
visitors)
The predicate is true for all Rockstar objects which have no related Person object whose personId is in the given list.

Generics method with predicated core data search won’t return correct result

I’m trying to have a generic method that search entities for a chosen string inside an attribute to see how many such entities are there, which then will decide what number or lack of number to attach to the string as it was returned. I’ve shown it in the picture. The entity type, the search string, and the attribute are all determined by input parameters. The problem is I cannot seem to make this generic method work consistently. On some projects it works, on others it doesn’t and the predicated search either returns an empty string or incomplete result. Can anyone help me figure it out? I’ve been scratching my head since last month.
Code:
private func getDefaultNameFor<T>Centityt T, defaultString: String, attribute: String)—>String{
var count = 0
var defaultName = defaultString
let type = T.self
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: “\(type)”)
let pred = NSPredicate(format: “%# CONTAINS[cd] %#", attribute, defaultString)
fetchRequest.predicate = pred
var untitledEntities: [NSManagedObject] = []
do {
untitledEntities = try context.fetch(fetchRequest)
}catch{
print("error fetching existing default names: \(error)”
}
count = untitledEntities.count
print("pred rEturns: " untitledEntities.description)
if count !=0 {
defaultName = defaultString + String(count)
}
print("default name: " defaultName)
return defaultName
}
Keypaths are substituted with %K instead of %#. (format: “%K CONTAINS[cd] %#", attribute, defaultString) worked.
Nothing wrong with using the generic, although I think not using it might still be preferable. This was a very basic sort of error I hope I had caught earlier by reviewing the predicate formatting guide.
Here’s how I solved it:
1: as suggested, I changed it from generic to taking a simple string as entity name, but the search still returns empty. I checked again at the pred but I don’t think it was formatted wrong. Am I missing something?
2: okay so I just adjusted the pred to be a simple format: “name CONTAINS[cd] ‘untitled’” and it worked fine. So why is it not working in (format: “%# CONTAINS[cd] %#", attribute, defaultString) ? I even tried to add ‘’ around the second %# but result was still empty.
3: (format: “name CONTAINS[cd] %#", defaultString) is working fine. So I guess the problem is the first substitute not functioning as keypath in the predicate format. So how to make it function as so?
4: Solved it! Keypaths are substituted with %K instead of %#. (format: “%K CONTAINS[cd] %#", attribute, defaultString) worked. This is such a basic error. I cannot believe I’ve been bugged with this for a month almost.

Can't Query Single CloudKit Record by recordName UUID

I have a CloudKit app where records are stored locally in CoreData. When creating the CoreData record, a UUID is generated and populated to the system recordName field. The app creates and saves records in CoreData then uploads the records to CloudKit. This works fine.
However, I am now coding to modify a record and am not able to fetch a record by the recordName UUID. I have not been successful setting a predicate to search for only that record. I can retrieve all records with TRUEPREDICATE and I can also retrieve a single record from a field I created in the CloudKit record type. I created a field called myRecordName where I store the same UUID as the CloudKit recordName. A query using the myRecordName works fine.
I have shown three methods below for the predicates. If p1 and p2 are not both commented out the app crashes on running. I assume I am missing something really simple here. Any guidance would be appreciated. Xcode 10.2.1, iOS 12.2, testing on a real device.
If either p1 or p2 or both are not commented out, the console shows:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CKReference rangeOfString:]:
unrecognized selector sent to instance 0x280878520'
func queryDatabase() {
let recordZone = CKRecordZone(zoneName: "PrimaryZone")
let recName = "0E763775-5AD3-4A50-BFB8-AC310180E8A2"
let recID = CKRecord.ID(recordName: recName, zoneID: recordZone.zoneID)
let searchRecord = CKRecord(recordType: "Patient", recordID: recID)
let p1 = NSPredicate(format: "%K == %#", CKRecord.Reference(recordID: recID, action: CKRecord_Reference_Action.none))
let p2 = NSPredicate(format: "%K == %#", CKRecord.Reference(record: searchRecord, action: CKRecord_Reference_Action.none))
//let predicate = NSPredicate(format: "TRUEPREDICATE")
let p3 = NSPredicate(format: "myRecordName = %#", recName)
let query = CKQuery(recordType: "Patient", predicate: p3)
let sortDescriptor = NSSortDescriptor(key: "lastNameText", ascending: true)
query.sortDescriptors = [sortDescriptor]
privateDatabase.perform(query, inZoneWith: recordZone.zoneID) { (results, error) in
if error != nil {
print("error querying database\(String(describing: error?.localizedDescription))")
} else {
DVC.ckRecords = results!
//print(results!)
print(DVC.ckRecords.first!)
}//if error else
}//perform query block
}//queryDatabase
I've never used CloudKit, so maybe I'm wrong, but…
The two problem lines of code, p1= and p2=, attempt to create a NSPredicate using a format string. Each format string contains two var arg substitutions, aka placeholders: %K and %#. So for this to work, that format string argument must be followed by two more arguments: first a key path, which should be a string (%K), and second a object for its value (%#). For your purposes here, the key path is probably just an attribute name.
But you have only provided one more argument, a CKRecord.Reference object, which the system tries to parse for the first (attribute name, string) argument. The error you are getting is typical of what happens when the system tries to parse an object such as CKRecord.Reference when it was expecting a string. Swift may be quite type safe but old-fashioned NSPredicate var args functions are not :)
To fix this problem (and move on to the next one), you should provide the key path (argument name) argument, something like this:
let p1 = NSPredicate(format: "%K == %#", "recordID", CKRecord.Reference(recordID: recID, action: CKRecord_Reference_Action.none))
let p2 = NSPredicate(format: "%K == %#", "recordID", CKRecord.Reference(record: searchRecord, action: CKRecord_Reference_Action.none))
Since I don't understand exactly what you are doing, you may need to tweak those two lines a bit. But the point is that, given your format string, you need three arguments to NSPredicate(format:), and the middle one needs to be a string representing a key path or attribute name.

How to validate NSPredicate before calling fetch

I create a search NSPredicate using a custom format string. Sometimes the syntax of this string is wrong and when I execute a fetch using a NSFetchRequest with this predicate I get a NSInvalidArgumentException, for example when the key-path is incorrect. I would much rather prefer to validate this predicate (call some method that returns YES if format is ok), than have an exception thrown that crashes my app. What can I do?
To rephrase the inquiry into a simple question:
Can I validate the predicate without potentially crashing the app?
Here is example code in Swift:
let text = "price > 0.1" // ok, no syntax error
let text = "price > 'abc'" // error
let predicate = NSPredicate.init(format: text, argumentArray: [])
let request = NSFetchRequest<Fruit>.init(entityName: "Fruit")
request.predicate = predicate
request.fetchLimit = 1
let fruit = try myContext.fetch(request) as [Fruit] // exception thrown here that crashes app
// sometimes the exception can be caught with an obj-c try/catch
// sometimes it can't be caught, and causes program to terminate
// CAN WE HAVE A SOLUTION THAT NEVER CAUSES A CRASH ?
There's no built-in way to validate that a predicate format string is a valid predicate. With format strings, you really need to validate the predicates by (a) testing them during development and (b) verifying that substitution values in predicates are at least the right data type.
If that's a problem, you might find it better to build your predicates in other ways, instead of with format strings. For example, use NSComparisonPredicate and NSCompoundPredicate to build the predicates from instances of NSExpression. The code will be much more verbose, but it will be easier to ensure that your predicate is valid by checking each part of it as you construct it.
You can make use of evaluate(with:) to validate NSPredicate's format,
Returns a Boolean value indicating whether the specified object matches the conditions specified by the predicate.
Swift 4.2:
For example,
let dataArray = [
"Amazon",
"Flipkart",
"Snapdeal",
"eBay",
"Jabong",
"Myntra",
"Bestbuy",
"Alibaba",
"Shopclue"
]
let searchString = "buy"
let predicate = NSPredicate(format: "SELF contains %#", searchString)
let searchResult = dataArray.filter { predicate.evaluate(with: $0) }
print(searchDataSource)
if !searchResult.isEmpty {
//success
} else {
//evaluation failed
}
To avoid an exception, you may want to use try-catch block

NSPredicate on multiple to-many relationships in Core Data

I have an NSPredicate which includes multiple aggregate filters, which is throwing an exception.
I have the following core data model:
I want to pick those ApparelItems for which any of the colours has an rgb of 13576743, and for which all of the picks have a pickTime earlier than a given NSDate.
My code to create the predicate is:
let request = NSFetchRequest(entityName: "ApparelItem")
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "ANY colours.rgb = 13576743"))
// find the NSDate representing midnight x days ago
let cal = NSCalendar.currentCalendar()
if let xDaysAgo = cal.dateByAddingUnit(.Day, value: -2, toDate: NSDate(), options: [])
{
let midnightXDaysAgo = cal.startOfDayForDate(xDaysAgo)
predicates.append(NSPredicate(format: "(ALL picks.pickTime < %#)", midnightXDaysAgo))
}
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
let searchData = try? objectContext.executeFetchRequest(request)
I get the following exception:
Exception name=NSInvalidArgumentException, reason=Unsupported predicate (ANY colours.rgb == 13576743) AND ALL picks.pickTime < CAST(479347200.000000, "NSDate")
I have tried:
Each individual predicate works fine. Ie ANY colours.rgb = ... works, also ALL picks.pickTime < ... works. They just don't work when combined into the same query.
Combining the two using into a single query, linked with AND, rather than using NSCompoundPredicate. Result is the same.
Is it possible that core data simply doesn't support filtering on more than one to-many relationship? That would seem odd. In which case how should I do the above?
Probably could try SUBQUERY() for NSPredicate.
The code below I came out from some guess and not very sure if it works or not. Usually it takes me trial and error many times for a to-many query clause with NSPredicate.
var predicates = [NSPredicate]()
predicates.append(NSPredicate(format: "SUBQUERY(colours, $colour, ANY $colour.rgb = 13576743).#count > 0"))
let cal = NSCalendar.currentCalendar()
if let xDaysAgo = cal.dateByAddingUnit(.Day, value: -2, toDate: NSDate(), options: []) {
let midnightXDaysAgo = cal.startOfDayForDate(xDaysAgo)
predicates.append(NSPredicate(format: "SUBQUERY(picks, $pick, ALL $pick.pickTime < %#).#count > 0", midnightXDaysAgo))
}
Colors and picks are two different entities, so you should be able to filter by both without using SUBQUERY. One predicate or a compound predicate should both be fine.
From the error message it seems that perhaps something is wrong with the date. Please check you have the expected data type in your managed object subclasses. If you used "primitive values" when creating the subclasses, you will need NSTimeInterval rather than NSDate.

Resources