Here is the real reason it doesn't work:
When you create a string using stringWithFormat: it will look like this:
@"engine like searchText"
Then you pass it to predicateWithFormat: which will see that both the left side and the right side of the comparison are unquoted strings, which means that it will interpret them as if they were key.
When this code runs, it will do:
id leftValue = [object valueForKey:@"engine"]; id rightValue = [object valueForKey:@"searchText"]; return (leftValue is like rightValue);
An exception caused by the fact that your object complains that it does not have a method named "searchText", which is correct.
Contrast this if you pulled out a call to stringWithFormat: and just put everything directly into predicateWithFormat: ::
NSPredicate *p = [NSPredicate predicateWithFormat:@"engine like %@", searchText];
predicateWithFormat: smart. He sees "the replacement pattern aha, a %@ ! This means that I can infer the argument from the argument list and paste it into the field and use it as is." This is exactly what he is going to do. It is going to infer the value of searchText from the argument list, put it in NSExpression and paste it directly into the parsing tree.
The most interesting thing is that the expression he expresses will be an expression of a constant value. This means that it is a literal meaning; not a key path, not a collection, etc. This will be a literal string. Then, when your predicate works, it will do:
id leftValue = [object valueForKey:@"engine"]; id rightValue = [rightExpression constantValue]; return (leftValue is like rightValue);
And this is correct, and therefore you should not pass stuff via stringWithFormat: before passing it to predicateWithFormat: