Сортировка NSArray, как MPMediaPickerController / iPod Library - PullRequest
2 голосов
/ 16 января 2011

Я разрабатываю пользовательский UIViewController для iPhone, который эмулирует подмножество MPMediaPickerController для файлов в локальном каталоге документов моего приложения. В частности, я пытаюсь воссоздать вкладку «Песни». Я успешно создал свой новый контроллер, за исключением того, что не могу отсортировать названия песен так, как они это делают в iPod Library или MPMediaPickerController. Вот пример того, как названия песен должны быть отсортированы:

  1. Удивительное название песни
  2. Cool Song
  3. Самая темная песня в истории
  4. Название моей песни
  5. Действительно классная песня
  6. Почему я?
  7. 4 часа впустую

Как видите, сортировка исключает ведущие статьи в названиях песен, а также помещает песни, которые начинаются с числового значения, в конце списка. Кто-нибудь может предложить эффективную функцию сортировки, которая учитывает эти правила?

Ответы [ 2 ]

4 голосов
/ 15 февраля 2011

Поскольку, похоже, никто не может предложить решение, я решил опубликовать решение, которое придумал. Сначала я создал модель для своих данных:

@interface MyModel : NSObject
{
   NSString* _value;
   NSString* _sortableValue;
}

@property (nonatomic,copy) NSString* value;

- (NSString*)sortableValue;
- (NSString*)comparableString:(NSString*)str;

@end

Ключом к модели является метод сопоставимого типа, который используется для создания sortableValue. Вот реализация модели:

@implementation MyModel

@synthesize value=_value;

-(void)dealloc
{
   [_value release];
   [_sortableValue release];

   [super dealloc];
}

- (void)setValue:(NSString*)value
{
   [_value release];
   _value = [value copy];
   [_sortableValue release];
   _sortableTValue = nil;
}

- (NSString*)sortableValue
{
   if (_sortableValue == nil)
      _sortableValue = [[self comparableString:_value] retain];

   return _sortableValue;
}

- (NSString*)comparableString:(NSString*)str
{
   if (str == nil)
      return nil;
   else if ([str length] == 0)
      return [NSString stringWithString:str];

   NSCharacterSet* numbersSet = [NSCharacterSet decimalDigitCharacterSet];
   if ([str rangeOfCharacterFromSet:numbersSet options:0 range:NSMakeRange(0, 1)].location != NSNotFound)
      return [NSString stringWithString:str];

   NSRange range = NSMakeRange(0, [str length]);

   if ([str compare:@"a " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 2)] == NSOrderedSame)
      range.location = 2;
   else if ([str compare:@"an " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 3)] == NSOrderedSame)
      range.location = 3;
   else if ([str compare:@"the " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 4)] == NSOrderedSame)
      range.location = 4;

   range.length -= range.location;

   NSCharacterSet* lettersSet = [NSCharacterSet letterCharacterSet];
   NSUInteger letterOffset = [str rangeOfCharacterFromSet:lettersSet options:0 range:range].location;
   if (letterOffset == NSNotFound)
      return [NSString stringWithString:str];

   letterOffset -= range.location;
   range.location += letterOffset;
   range.length -= letterOffset;

   return [str substringWithRange:range];
}

@end

Помимо удаления лидирующих статей из строки, также удаляются все лидирующие не буквенные символы. В моей библиотеке iPod есть песня под названием «$ ell Your $ oul», которая заканчивается в разделе E в MPMediaPickerController. Я не уверен, что это было бы то, что я сделал бы, если бы я написал первоначальный алгоритм сортировки, но я собирался согласовать его с MPMediaPickerController, так что вы идете.

Последняя часть головоломки - класс UILocalizedIndexedCollation. Этот удобный маленький вспомогательный класс поможет вам отсортировать данные, чтобы упростить их передачу в UITableView через UITableViewDataSource. Вот фрагмент о том, как использовать класс UILocalizedIndexedCollation вместе с моделью:

   // tableData will contain an NSArray for each populated section in the table view
   NSMutableDictionary* tableData = [NSMutableDictionary dictionary];

   NSMutableArray* myArray = [NSMutableArray array];
   // Populate myArray with instances of MyModel

   UILocalizedIndexedCollation* indexer = [UILocalizedIndexedCollation currentCollation];
   for (MyModel* data in myArray)
   {
      NSInteger index = [indexer sectionForObject:data collationStringSelector:@selector(sortableValue)];
      NSNumber* key = [[NSNumber alloc] initWithInteger:index];
      NSMutableArray* array = [tableData objectForKey:key];
      if (array == nil)
      {
         array = [NSMutableArray new]; // Will be released after creating a sorted array in the following section
         [tableData setObject:array forKey:key];
      }

      [array addObject:data];
      [key release];
   }

   [tableData enumerateKeysAndObjectsUsingBlock:^(id key, id array, BOOL* stop)
   {
      NSMutableArray* sortedArray = [[indexer sortedArrayFromArray:array collationStringSelector:@selector(sortableValue)] mutableCopy];
      [tableData setObject:sortedArray forKey:key];
      [array release];
   }];

Одна быстрая заметка о UILocalizedIndexedCollation (из документации Apple):

Если приложение предоставляет Файл Localizable.strings для текущие языковые предпочтения, объект индексированного сопоставления локализуется каждая строка возвращается методом идентифицируется селектором.

Поэтому убедитесь, что вы предоставили строки Localizable.string для каждого языка, который хотите поддерживать, иначе в табличном представлении будут только разделы A-Z и #.

Мне потребовалось время, чтобы проработать все детали этого, поэтому я надеюсь, что это станет полезным для других людей. Если вы видите какие-либо способы, которыми я могу улучшить это, пожалуйста, дайте мне знать!

0 голосов
/ 19 февраля 2011

Вам также может понадобиться рассмотреть некоторые символы с акцентами, такие как è, é, ò, à, ù, ì.

Так что я немного изменил ваш код, чтобы включить это. Ваш код - большой вклад для всех нас, разработчиков iphone

    - (NSString*)comparableString:(NSString*)str
{
    if (str == nil)
        return nil;
    else if ([str length] == 0)
        return [NSString stringWithString:str];

    NSCharacterSet* numbersSet = [NSCharacterSet decimalDigitCharacterSet];
    if ([str rangeOfCharacterFromSet:numbersSet options:0 range:NSMakeRange(0, 1)].location != NSNotFound)
        return [NSString stringWithString:str];

    NSRange range = NSMakeRange(0, [str length]);

    if ([str compare:@"a " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 2)] == NSOrderedSame)
        range.location = 2;
    else if ([str compare:@"an " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 3)] == NSOrderedSame)
        range.location = 3;
    else if ([str compare:@"the " options:(NSAnchoredSearch|NSCaseInsensitiveSearch) range:NSMakeRange(0, 4)] == NSOrderedSame)
        range.location = 4;

    range.length -= range.location;

    NSCharacterSet* lettersSet = [NSCharacterSet letterCharacterSet];
    NSUInteger letterOffset = [str rangeOfCharacterFromSet:lettersSet options:0 range:range].location;
    if (letterOffset == NSNotFound)
        return [NSString stringWithString:str];

    letterOffset -= range.location;
    range.location += letterOffset;
    range.length -= letterOffset;



//my modification starts here.........

    NSString * finalString = [str substringWithRange:range];
    NSString * firstCharString = [finalString substringToIndex:1];

    NSData * encodedData = [firstCharString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
    NSString * encodedString = [[NSString alloc] initWithBytes:[encodedData bytes] length:[encodedData length] encoding:NSASCIIStringEncoding];
    if ([encodedString isEqualToString:@"?"]) {
        return finalString;
    }

    NSString * finalProcessedString = [finalString stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:encodedString];
    [encodedString release];
    return finalProcessedString;
}
...