Predicate comparing two keys - ios

I am trying to write a predicate to compare two keys in a cloud kit database. All the documentation I've been able to find only allows you to compare one key against a set value. Is there a way to write a predicate in swift to compare two keys?
The closest I've been able to come up with is:
let pred = NSPredicate(format: "%K == %K", "keyA", "keyB")!
but that keeps crashing on me.

use %# on R.H.S side insted of %k like this..
%# is a var arg substitution for an object value—often a string, number, or date.
%K is a var arg substitution for a key path.
let pred = (NSPredicate(format: "%K == %#", "keyA", "keyB")!)

Are you really trying to compare two keys? Or are you trying to compare the value associated with two keys? The difference between %# and %K in a predicate is that %# will be appropriately escaped, while %K won't be.
So:
NSPredicate(format:"value = %#", "Fred") // yields 'value = "Fred"'
While:
NSPredicate(format:"value = %K", "Fred") // yields 'value = Fred'
You want to use %K when you're passing an key name in as a parameter to the predicate, while you want to use '%#' if you're passing in a value.
From your example, title, and question, it's tough to figure out what you're actually trying to do, if you a predicate that insures that the value of two fields/keys is the same, you want to use
NSPredicate(format:"%K = %K", "key1", "key2")
as you're doing.
If you're trying to check that a particular key has a particular value, use:
NSPredicate(format:"%K = %#", "key1", "value1")
The code you've given, in isolation, executes just fine, where and what exactly is crashing, give us a bit more context.
With the further information that you're wanting to filter for records containing the same value in two different fields, it appears that CloudKit doesn't support field OP field only field OP constant Given that, the best work around I can suggest is:
let predicate = NSPredicate(format: "(gender1 = \"male\" AND gender2 = \"male\") OR (gender1 = \"female\" AND gender2 = \"female\")")

Related

How to fetch data using predicate with the ALL ... IN format string

So I have an core data entity 'transaction' with an attribute 'tags' which related to another entity 'tag'. The relationship type between the two entities is many to many. Which means a transaction can have many tags and a tag can have many transactions.
Now, I've created a method that will fetch transaction entries based on specific tags. The method looks similar to below:
func transactions(withTags tags: [Tag] = [], matchCriteria: String = "ANY") {...}
So in the function, I have used a predicate like the below:
predicate = NSPredicate(format: "(%# <= date) AND (date < %#) AND (\(matchCriteria) tags IN %#)", argumentArray: [startDate, endDate, tags])
So the key element in the predicate is the "ANY tags IN %#", which just basically searches for the given tag(s) and if any of those tags are found in the entry, then there would be a match. This predicate works perfectly for that purpose.
But now, what I want to do is to have a similar predicate but, I would like it to return a match only when ALL of the given tags are found. For example, if I have 2 tags e.g. 'planned' and 'paid'. Then only entries with both tags should be returned.
So, based on the Apple documentation here https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html#//apple_ref/doc/uid/TP40001795, it appears there's an "ALL" operator. So I tried creating a predicate similar to the below:
predicate = NSPredicate(format: "(%# <= date) AND (date < %#) AND (ALL tags IN %#)", argumentArray: [startDate, endDate, tags])
Just substituted ANY with ALL. But it doesn't seem to work and it crashes my app. The error I get is "Thread 1: Exception: "Unsupported predicate...". This is where I'm stuck, and hope that you guys can point me towards the right direction.
What should the correct predicate be for me to satisfy the above requirements?
First just a note that, even if it didn't crash, your predicate ("ALL tags in %#", tags) wouldn't do quite what you want. It would ensure that a transaction is included in the results only if all its tags are in the list. But if it had only one tag, that matched only one from your list, it would still be included. What you need is the reverse ("ALL %# IN tags"). But that will cause the same crash.
The easiest way to achieve what you want is to prepare a compound predicate, taking each tag from your list in turn, eg:
"(ANY tags == 'planned') AND (ANY tags == 'paid')"
You can construct this fairly quickly for your tags array:
let datePredicateArray = [NSPredicate(format: "(%# <= date) AND (date < %#)", argumentArray: [startDate, endDate])]
let tagPredicateArray = tags.map {NSPredicate(format:"ANY tags == %#", $0)}
let allPredicates = datePredicateArray + tagPredicateArray
let finalPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: allPredicates)

Use CONTAINS or ANY in CloudKit predicate with an array of comparison

I tried to use NSCompoundPredicate(orPredicateWithSubpredicates:) with CloudKit NSPredicate but then I read on Apple's documentation that OR comparison predicates aren't supported in CloudKit so I hit a road-block with this.
I have an array of CKReferences that I need to see if a Record-type of Reference list contains those references.
I'm struggling on how to assemble the predicate itself , because I'm trying to avoid nested-queries in the completionBlock.
let's say I have the following array of references:
let refs = [CKReference]() // in my case the array isn't empty
I tried the following but it didn't worked, because somehow it only looks for the first object of the array, and not the whole array.
let predicate = NSPredicate(format: "tableName CONTAINS %#", argumentArray: refs)
Here if I print the predicate it prints:
tableName CONTAINS CKReference: 0x600000229c40; 20B54862-46CC-405F-BAE8-0DC8D3A52F78:(_defaultZone:defaultOwner)>
As you can see, it only looks for the first object in the array.
It may work if I use the ANY operator in the predicate, something like:
let predicate = NSPredicate(format: "ANY { '%#', '%#' } = tableName", args: refs[0], refs[1])
But my problem here is how can I build that predicate, since the refs array is dynamic and I don't know how many objects it may contain , and by that I don't know how to build the predicate args accessing the [0], [1], ... of the refs array.
Do you have any workaround with this? Or a best way to approach this issue?
Thank you.
EDIT
I figure out a way to solve this issue, but I don't know if that's the most efficient one, so I'm still opened to answers and opinions.
Here's my temporary solution:
for (i, reference) in refs.enumerated() {
let predicate = NSPredicate(format: "tableName CONTAINS %#", reference)
// CKQuery
// CKQueryOperation
// Database.add(CKQueryOperation)
if i == refs.count - 1 {
// UPDATE UI
}
}
NSMutableArray * tagsReferencesArray = [[NSMutableArray alloc] init];
[tagsReferencesArray addObject:tag100_Reference];
[tagsReferencesArray addObject:tag300_Reference];
[tagsReferencesArray addObject:tag200_Reference];
predicate= [NSPredicate predicateWithFormat:#"ANY %# in field_TagsReferenceList ", tagsReferencesArray];

What is syntax for placeholder usage in regular expression in Predicate?

I want to specify my value in regular expression, how can I do it?
What I am doing:
request.predicate = Predicate(format: "entityProperty MATCHES [c] '[^0123]%#'", myValue)
Observing the result, I can say that %# is parsed as a characters in the regex string, no value is placed instead of it
Here is some explanation on usage of regular expressions with predicates, but no information about placeholders for values
Your Solution would work for:
myValue is representing valid regex pattern.
myValue may not contain single quotes (').
If myValue may contain single quote, this would work:
request.predicate = NSPredicate(format: "entityProperty MATCHES [c] %#", "[^1234]" + myValue)
And if myValue is not a regex pattern and may contain some meta-characters, you may need to write something like this:
request.predicate = NSPredicate(format: "entityProperty MATCHES [c] %#", "[^1234]"+NSRegularExpression.escapedPattern(for: myValue))
Anyway, you need to remember:
Single or double quoting variables (or substitution variable strings)
cause %#, %K, or $variable to be interpreted as a literal in the
format string and so prevent any substitution.
Parser Basics (Predicate Programming Guide)
(I used NSPredicate as Predicate is not available in Xcode 8 beta 6.)
request.predicate = Predicate(format: "entityProperty MATCHES [c] '[^0123]\(myValue)'")
Solution:
instead of %# placeholder use string substitution:
"prefixSomeText\(myValue)suffixSomeText"

NSPredicate crash on CONTAINS?

I'm trying to do a simple predicate filter on an array of objects.
The objects in the array have 2 properties, displayValue and value. I am trying to filter based on a search string and I get a crash.
NSPredicate *pred = [NSPredicate predicateWithFormat:#"displayValue CONTAINS[cd] %#", searchString];
NSArray *results = [_data filteredArrayUsingPredicate:pred];
what exactly is incorrect about this format that it causes a Can't use in/contains operator with collection 100 (not a collection) crash?
I was able to reproduce your problem. This happens if the displayValue of one of the objects
is not a NSString, but some different type.
From your error message, I assume that you assigned an NSNumber, for example
obj.displayValue = #100;
somewhere in your code. The "CONTAINS" predicate works only with strings, so you must assign
only string values to the property.
Therefore, you should define the type of the property as
NSString * instead of id, and check the assignments to that property.
If you really need to keep the id type and store different kinds of objects in that property,
then you cannot use the "CONTAINS" operator in the predicate. A direct comparison
with #"displayValue == %#" would work, however.
UPDATE: As a workaround, you could use the description method, which converts any object
to a string, in particular it converts a NSNumber to its string representation. So the following could work:
[NSPredicate predicateWithFormat:#"displayValue.description CONTAINS[cd] %#", searchString];
The drawback is that the exact description format is not documented.
Another solution could be to use a block-based predicate, where you can check the type
of each object and perform the appropriate comparison.

NSPredicate predicateWithFormat passing in name of attribute

Simple question regarding NSPredicate's. I'm trying to construct my predicate with "passed in" values like so :
NSPredicate* currentPredicate = [NSPredicate predicateWithFormat:#"%# == %#",key,[changesDict valueForKey:#"Id"] ];
However, I haven't been able to get this to work correctly. If I insert the actual value I pass through it does work though. So this works :
NSPredicate* currentPredicate = [NSPredicate predicateWithFormat:#"contactId == %#",[changesDict valueForKey:#"Id"] ];
(Notice i inserted contactId as opposed to the previous example where I pass a string by the same name)
To troubleshoot I NSLogged the two predicates looking at their descriptions and they were different. I'll show them below.
This is the working one
2013-01-17 10:29:25.513 TestingCoreData[1776:3b03] contactId == "5878"
This is the non working one
2013-01-17 10:29:25.513 TestingCoreData[1776:3b03] "contactId" == "5878"
So I can kind of see that it's inserting a string where I really just want the name of the attribute that i'll later be using in the fetch request. But is there any way to accomplish this by passing in values?
For keys or key paths, you have to use the %K format:
[NSPredicate predicateWithFormat:#"%K == %#", key, [changesDict valueForKey:#"Id"]];
(See Parser Basics in the "Predicate Programming Guide".)

Resources