Search code examples
objective-cnspredicatestringwithformat

Unable to Parse Format String in NSPredicate Using NSDates


I have read numerous Stack Overflow posts about possible errors in an NSPredicate statement, and yet I still can't figure out what's wrong with my code.

I'm checking to see if a user is searching on a year with some regex. If they are, I create a search to go from the first day of the year to the last day.

Here's my code:

//Check if searchBar.text is a year
NSString *expression = @"^\\d{4}$";
NSError *error = NULL;

//Regex test
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:expression options:NSRegularExpressionCaseInsensitive error:&error];
NSTextCheckingResult *match = [regex firstMatchInString:searchBar.text options:0 range:NSMakeRange(0, [searchBar.text length])];

NSString *predicateString;
if(match){

  //Search contains a year
  NSDate *startDate = [_fullFormat dateFromString:[NSString stringWithFormat:@"%@-01-01",searchBar.text]];
  NSDate *endDate = [_fullFormat dateFromString:[NSString stringWithFormat:@"%@-12-31",searchBar.text]];

  predicateString = [NSString stringWithFormat:@"(departure CONTAINS[cd] '%1$@') OR (destination CONTAINS[cd] '%1$@') OR (aircraft.aircraftRegistration CONTAINS[cd] '%1$@') OR (aircraft.makeModel CONTAINS[cd] '%1$@') OR (duration CONTAINS[cd] '%1$@') OR (remarks CONTAINS[cd] '%1$@') OR ((flightDate >= %2$@) AND (flightDate <= %3$@))",searchBar.text,startDate,endDate];

}else{
  //...
}

NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:predicateString];

Logging predicateString yields:

(departure CONTAINS[cd] '2014') OR (destination CONTAINS[cd] '2014') OR (aircraft.aircraftRegistration CONTAINS[cd] '2014') OR (aircraft.makeModel CONTAINS[cd] '2014') OR (duration CONTAINS[cd] '2014') OR (remarks CONTAINS[cd] '2014') OR ((flightDate >= 2014-01-01 07:00:00 +0000) AND (flightDate <= 2014-12-31 07:00:00 +0000))

I'm getting a crash with the following statement:

'Unable to parse the format string "(departure CONTAINS[cd] '2014') OR (destination CONTAINS[cd] '2014') OR (aircraft.aircraftRegistration CONTAINS[cd] '2014') OR (aircraft.makeModel CONTAINS[cd] '2014') OR (duration CONTAINS[cd] '2014') OR (remarks CONTAINS[cd] '2014') OR ((flightDate >= 2014-01-01 07:00:00 +0000) AND (flightDate <= 2014-12-31 07:00:00 +0000))"'

Any idea what I'm doing wrong?


Solution

  • You should not use stringWithFormat to build a predicate. Your predicateString contains the description strings of the dates, such as "2014-01-01 07:00:00 +0000", and predicateWithFormat cannot handle that.

    Unfortunately, predicateWithFormat cannot handle positional parameters like stringWithFormat, which means that you have to repeat the arguments where necessary:

    [NSPredicate predicateWithFormat:@"(departure CONTAINS[cd] %@) OR (destination CONTAINS[cd] %@) OR (aircraft.aircraftRegistration CONTAINS[cd] %@) OR (aircraft.makeModel CONTAINS[cd] %@) OR (duration CONTAINS[cd] %@) OR (remarks CONTAINS[cd] %@) OR ((flightDate >= %@) AND (flightDate <= %@))",
        searchBar.text, searchBar.text, searchBar.text, searchBar.text,
        searchBar.text, searchBar.text, startDate, endDate];
    

    For complex predicates, you might also consider to use the NSCompoundPredicate methods andPredicateWithSubpredicates: and orPredicateWithSubpredicates: to build the predicate successively.