こんなファイルがあったとする。
file-a file-1 file-01 file-2 file-10 file-b file-ab
辞書式(普通の昇順ソート)で並べ替えると
file-01 file-1 file-10 file-2 file-a file-ab file-b
となるわけだが、Finderでソートすると
file-1 file-01 file-2 file-10 file-a file-ab file-b
となる。連続した数字部分を数値と見なし、数値順で並べてくれる。個人的にはこの方が分かりやすくて好きだ。
で、この数値順ソートをCocoaで実現するにはどーしたらいいのかなー?と思って調べていたら、Technical Q&A QA1159: Sorting Like the Finderという、そのまんまの記事がADCにあった。流石林檎様、分かっていらっしゃる。
上記Q&Aのコードを改造してNSMutableArray
のカテゴリメソッドにするとスマートに使えて良い感じ。
NSString
配列のソートはもちろん、任意のオブジェクトの場合はソートに使うNSString
インスタンス変数名をsortByFinderOrderWithStringObjectKey:
に渡せばおk。
#include <CoreServices/CoreServices.h> #include <sys/param.h> static CFComparisonResult CompareLikeTheFinder(const void *val1, const void *val2, void *context) { SInt32 compareResult; CFStringRef lhsStr; CFStringRef rhsStr; CFIndex lhsLen; CFIndex rhsLen; UniChar lhsBuf[MAXPATHLEN]; UniChar rhsBuf[MAXPATHLEN]; // val1 is the left-hand side CFString. // val2 is the right-hand side CFString. if (context) { NSString *key = (NSString *)context; lhsStr = (CFStringRef)[(NSObject *)val1 valueForKey:key]; rhsStr = (CFStringRef)[(NSObject *)val2 valueForKey:key]; } else { lhsStr = (CFStringRef)val1; rhsStr = (CFStringRef)val2; } lhsLen = CFStringGetLength(lhsStr); 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. 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 SortCFMutableArrayLikeTheFinder(CFMutableArrayRef array, CFStringRef key) { CFArraySortValues( array, CFRangeMake(0, CFArrayGetCount(array)), CompareLikeTheFinder, key ); } @interface NSMutableArray (PKAdditions) - (void)sortByFinderOrder; - (void)sortByFinderOrderWithStringObjectKey:(NSString *)key; @end @implementation NSMutableArray (PKAdditions) - (void)sortByFinderOrder { [self sortByFinderOrderWithStringObjectKey:nil]; } - (void)sortByFinderOrderWithStringObjectKey:(NSString *)key { SortCFMutableArrayLikeTheFinder(self, key); } @end
実際のファイル名やディレクトリ名をソートする場合は、- [NSFileManager displayNameAtPath:(NSString *)path]
で得られる名前をソートしないとFinder順にはならない。なぜかというと、Finderから見えるディレクトリ名はローカライズ(~/Documents → 書類 みたいなの)された物なので。