Filtering arrays with NSPredicate

I think my first experience of using the NSPredicate class on the iPhone was with core data queries but it is has many other useful applications. I had the need the other day to process a list of filenames and match against a simple regular expression. The code to create the array of filenames that I needed to process looked something like this:

NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *defaultPath = [[NSBundle mainBundle] resourcePath];
NSError *error;

NSArray *directoryContents = [fileManager contentsOfDirectoryAtPath:defaultPath

The first few lines just setup a search of the resource directory in the main application bundle so that the array directoryContents ends up containing the filenames of all the files in the directory. I then need to process that array and pull out the filenames that match my pre-defined regular expression.

NSPredicate Format Strings

The NSPredicate class provides a general way to specify a query that can then be applied to filter an array. The actual query to use is specified when creating the predicate as an argument to the predicateWithFormat: method. Some examples will hopefully make it clear:

Simple match

The simplest example is when you just need an exact match against a single value. This is a fairly meaningless example in this case but illustrates the basic technique:

NSString *match = @"imagexyz-999.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF == %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];

The query string is simply “SELF == %@” where SELF refers to the each element in the array. To use the predicate we apply it to the array of file names using filterArrayUsingPredicate:. In this trivial (and pointless) example the results array would contain a single string “imagexyz-999.png” assuming that file existed in the directory.

Wildcard match

To match using a wildcard string the query can use like instead of the comparison operator and include the “*” (match zero or more characters) or “?” (match exactly 1 character) as wildcards as follows:

NSString *match = @"imagexyz*.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF like %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];

In this case the results array would contain the filenames that match imagexyz*.png (e.g. imagexyz-001.png, imagexyz-002.png, etc.).

Case Insensitive match

To make the previous example use a case insensitive match the like operator is modified to like[c]. There is also an additional option to match in a diacritic-insenstive way to ignore accented characters by using like[d]. You can combine these two options as follows:

NSString *match = @"imagexyz*.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF like[cd] %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];

Other string comparisons

In addition to like you can also use beginswith, endswith, contains and matches. These are mostly self-explanatory with the exception perhaps of matches. With matches you can specify a full regular expression which allows more complex matches than is possible with just the * and ? wildcard characters. For example to match filenames of the form imagexyz-ddd.png where ddd are all digits:

NSString *match = @"imagexyz-\\d{3}\\.png";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF matches %@", match];
NSArray *results = [directoryContents filteredArrayUsingPredicate:predicate];

I will not try to explain regular expression syntax here but note that the backslash () characters are escaped by using a double-backslash. Also note that strict ICU regular expression formats are used.

Never Miss A Post

Sign up to get my iOS posts and news direct to your inbox and I'll also send you my free iOS Size Classes Cheat Sheet

    Unsubscribe at any time. See Privacy Policy.

    Archives Categories