Sorting Like the Finder

Q: My application displays a list of files to the user. How can I sort this list like the Finder?

A: There is no specific system routine to sort a list of strings as the Finder does. However, the Finder uses a standard system routine, UCCompareTextDefault, to compare file names, and you can use this routine to sort your own list. Listing 1 shows how to sort an array of strings like the Finder by combining CFArraySortValues with a comparison function that uses UCCompareTextDefault.

Listing 1: Comparison function to sort like the Finder

#include <CoreServices/CoreServices.h>
#include <sys/param.h>

static CFComparisonResult CompareLikeTheFinder(
    const void *    val1,
    const void *    val2,
    void *          context
)
{
    #pragma unused(context)
    SInt32          compareResult;
    CFStringRef     lhsStr;
    CFStringRef     rhsStr;
    CFIndex         lhsLen;
    CFIndex         rhsLen;
    UniChar         lhsBuf[MAXPATHLEN];
    UniChar         rhsBuf[MAXPATHLEN];

    // val1 is the left-hand side CFString.

    lhsStr = (CFStringRef) val1;
    lhsLen = CFStringGetLength(lhsStr);

    // val2 is the right-hand side CFString.

    rhsStr = (CFStringRef) val2;
    rhsLen = CFStringGetLength(rhsStr);

    // Get the actual Unicode characters (UTF-16) for each string.

    CFStringGetCharacters( lhsStr, CFRangeMake(0, lhsLen), lhsBuf);
    CFStringGetCharacters( rhsStr, CFRangeMake(0, rhsLen), rhsBuf);

    // Do the comparison.

    (void) UCCompareTextDefault(
          kUCCollateComposeInsensitiveMask
        | kUCCollateWidthInsensitiveMask
        | kUCCollateCaseInsensitiveMask
        | kUCCollateDigitsOverrideMask
        | kUCCollateDigitsAsNumberMask
        | kUCCollatePunctuationSignificantMask,
        lhsBuf,
        lhsLen,
        rhsBuf,
        rhsLen,
        NULL,
        &compareResult
    );

    // Return the result.  Conveniently, UCCompareTextDefault
    // returns -1, 0, or +1, which matches the values for
    // CFComparisonResult exactly.

    return (CFComparisonResult) compareResult;
}

static void SortCFMutableArrayOfCFStringsLikeTheFinder(
    CFMutableArrayRef strArray
)
{
    CFArraySortValues(
        strArray,
        CFRangeMake(0, CFArrayGetCount(strArray)),
        CompareLikeTheFinder,
        NULL
    );
}

Note: The code in Listing 1 is sample code that is not optimized for speed. There are a number of steps that you can take to make this code run faster.

  • You could reduce string conversion overhead by storing the strings as UTF-16, or by converting all of the strings before starting the sort.

  • You could speed up the string comparison by using collation keys (see the documentation for UCGetCollationKey and UCCompareCollationKeys).

Note: The use of MAXPATHLEN in Listing 1 is a shortcut that simplifies the code for readers who are more interested in sorting than in file system esoterica. MAXPATHLEN is a constant (defined in <sys/param.h>) that represents the maximum path length supported by the system, measured as a count of UTF-8 characters. As a) the length of a single name component can't exceed the length of the path as a whole, and b) UTF-16 is always a more efficient packing of Unicode that UTF-8, MAXPATHLEN represents a convenient upper bound for the length of a file name.

I could, and in production quality code I would, get the length of the string and allocate an appropriately sized buffer. However, that would be overkill for a sample whose focus is on how to sort file names. Moreover, in production code I would prefer to preconvert all of the strings to UTF-16 rather than converting them for each string comparison.

Document Revision History

DateNotes
2004-10-27Shows how to sort strings like the Finder's list view.

Posted: 2004-10-27


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.