iPhone联系app风格的索引表视图实现

时间:2010-06-04 06:38:15

标签: iphone nsfetchedresultscontroller indexed-view nssortdescriptor custom-compare

我的要求: 我有这样一个直接的要求,即在索引表视图中按字母顺序列出人名,其中索引标题是字母表的起始字母(另外顶部是搜索图标,#显示以数字和其他特殊字母开头的misc值字符)。

到目前为止我做了什么: 1.我使用核心数据进行存储,“last_name”被建模为Contacts实体中的String属性 2.我正在使用NSFetchedResultsController来显示已排序的索引表视图。

完成我的要求的问题: 首先,我无法将部分索引标题作为字母表的第一个字母。戴夫在以下帖子中提出的建议帮助我实现了同样的目标:NSFetchedResultsController with sections created by first letter of a string

我遇到Dave建议的唯一问题是我无法将名为misc的名称归入“#”索引。

我尝试了什么: 1.我尝试将自定义比较方法添加到NSString(类别)以检查比较和部分是如何进行的,但是在NSSortDescriptor选择器中指定时不会调用自定义方法。

以下是一些代码:

@interface NSString (SortString)

-(NSComparisonResult) customCompare: (NSString*) aStirng;

@end

@implementation NSString (SortString)

-(NSComparisonResult) customCompare:(NSString *)aString
{
 NSLog(@"Custom compare called to compare : %@ and %@",self,aString);
 return [self caseInsensitiveCompare:aString];
}

@end

获取数据的代码:

NSArray *sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:@"last_name"
               ascending:YES selector:@selector(customCompare:)] autorelease]];

  [fetchRequest setSortDescriptors:sortDescriptors];
        fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
            managedObjectContext:managedObjectContext sectionNameKeyPath:@"lastNameInitial" cacheName:@"MyCache"];

你能让我知道我错过了什么以及如何完成这项要求吗?

2 个答案:

答案 0 :(得分:4)

在这个问题上,这是一个非常低效的首次通过,我最终将重写。但希望这会对你有所帮助。

这样做的想法是“保证”在点击“标准”部分索引视图时返回真正的表部分索引。标准截面索引视图应具有用于搜索的放大镜图标,用于非字母部分的散列标记(#)以及用于字母部分的字母A到Z.

无论实际部分有多少或它们是由什么构成的,都会显示此标准视图。

最终,此代码将截面视图索引映射到获取的结果控制器中的实际存在的字母部分名称路径,或映射到实际存在的非字母(数字)部分,或映射到表头中的搜索字段。

用户只会在每次触摸部分索引时偶尔重新创建部分索引映射数组(_idxArray),但在每次触摸时重新创建数组显然效率低下,可以调整以缓存预先计算的结果。

有很多地方可以开始收紧这个问题:例如,我可以从一开始就将sectionIndexTitleLetters静态字符串全部置为大写。不过,它在3GS手机上足够快,所以我最近没有重新考虑过这个问题。

在标题中:

static NSString *sectionIndexTitleLetters = @"abcdefghijklmnopqrstuvwxyz";

在表视图数据源的实现中:

- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tv {
    if (tv != searchDisplayController.searchResultsTableView) {
        NSMutableArray *_indexArray = [NSMutableArray arrayWithCapacity:([sectionIndexTitleLetters length]+2)];

        [_indexArray addObject:@"{search}"];
        [_indexArray addObject:@"#"];

        for (unsigned int _charIdx = 0; _charIdx < [sectionIndexTitleLetters length]; _charIdx++) {
            char _indexChar[2] = { toupper([sectionIndexTitleLetters characterAtIndex:_charIdx]), '\0'};
            [_indexArray addObject:[NSString stringWithCString:_indexChar encoding:NSUTF8StringEncoding]];
        }

        return _indexArray;
    }
    return nil;
}

- (NSInteger) tableView:(UITableView *)tv sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    if (tv != searchDisplayController.searchResultsTableView) {
        if (index == 0) {
            //
            // This is the search bar "section"
            //
            [currentTableView scrollRectToVisible:[[currentTableView tableHeaderView] bounds] animated:YES];
            return -1;
        }
        else if (index == 1) {
            //
            // This is the "#" section, which covers non-alphabetic section headers (e.g. digits 0-9)
            //
            return 0;
        }
        else {
            //
            // This is a bit more involved because the section index array may contain indices that do not exist in the 
            // fetched results controller's sections->name info.
            //
            // What we are doing here is building a "fake-index" array that will return a real section index regardless of 
            // whether the section index title being touched exists or not. 
            //
            // The fake array will be of length of the section index title array, and each index will contain an unsigned 
            // integer from 1 to {numOfRealSections}. 
            //
            // The value this array returns will be "nearest" to the real section that is in the fetched results controller.
            //

            NSUInteger _alphabeticIndex = index-2;

            unsigned int _idxArray[26];
            for (unsigned int _initIdx = 0; _initIdx < [sectionIndexTitleLetters length]; _initIdx++) {
                _idxArray[_initIdx] = [[fetchedResultsController sections] count] - 1;
            }

            unsigned int _previousChunkIdx = 0;
            NSNumberFormatter *_numberFormatter = [[NSNumberFormatter alloc] init];
            NSLocale *_enUSLocale = [[NSLocale alloc] initWithLocaleIdentifier: @"en_US"];
            [_numberFormatter setLocale:_enUSLocale];
            [_enUSLocale release];

            for (unsigned int _sectionIdx = 0; _sectionIdx < [[fetchedResultsController sections] count]; _sectionIdx++) {
                NSString *_sectionTitle = [[[fetchedResultsController sections] objectAtIndex:_sectionIdx] name];
                if (![_numberFormatter numberFromString:_sectionTitle]) {
                    // what's the index of the _sectionTitle across sectionIndexTitleLetters?
                    for (unsigned int _titleCharIdx = 0; _titleCharIdx < [sectionIndexTitleLetters length]; _titleCharIdx++) {
                        NSString *_titleCharStr = [[sectionIndexTitleLetters substringWithRange:NSMakeRange(_titleCharIdx, 1)] uppercaseString];
                        if ([_titleCharStr isEqualToString:_sectionTitle]) {
                            // put a chunk of _sectionIdx into _idxArray
                            unsigned int _currentChunkIdx;
                            for (_currentChunkIdx = _previousChunkIdx; _currentChunkIdx < _titleCharIdx; _currentChunkIdx++) {
                                _idxArray[_currentChunkIdx] = _sectionIdx - 1;
                            }
                            _previousChunkIdx = _currentChunkIdx;
                            break;
                        }
                    }               
                }
            }

            [_numberFormatter release];

            return (NSInteger)_idxArray[_alphabeticIndex];
        }
    }
    return 0;
}

答案 1 :(得分:1)

我可能天真,但我不明白为什么这些解决方案如此巴洛克式。我这样做了:

在我的模型中,我添加了一个方法:

-(NSString *)lastInitial {
    return [self.lastname substringToIndex:1];
}

在我的tablecontroller中,我将fetchedresultscontroller设置为使用该方法:

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"lastInitial" cacheName:@"Master"];

似乎工作 - 这是一个坏主意的原因吗?或者我从ios5中的新功能中获益还是什么?