【问题标题】:iPhone contacts app styled indexed table view implementationiPhone联系人应用程序样式的索引表视图实现
【发布时间】:2011-02-27 15:46:22
【问题描述】:

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

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

满足我要求的问题: 1. 首先,我无法让章节索引标题成为字母的第一个字母。 Dave 在以下帖子中的建议帮助我实现了同样的目标:NSFetchedResultsController with sections created by first letter of a string

我在 Dave 的建议中遇到的唯一问题是我无法将 misc 命名为分组在“#”索引下。

我尝试过的: 1. 我尝试向 NSString (category) 添加自定义比较方法来检查比较和部分是如何进行的,但是当在 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"];

你能告诉我我缺少什么以及如何满足要求吗?

【问题讨论】:

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


    【解决方案1】:

    这是一个非常低效的第一次解决这个问题,我最终将重写。但希望这会对你有所帮助。

    这样做的目的是“保证”在点击“标准”部分索引视图时获得真正的表部分索引。标准的部分索引视图应该有一个用于搜索的放大镜图标,一个用于非字母部分的井号 (#),以及用于字母部分的字母 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;
    }
    

    【讨论】:

    • 嗨,亚历克斯,感谢您的帮助。上面的代码确实有效。但是 fetchedresultscontroller 部分并不如预期。例如,如果有两个姓氏值分别为 111 和 222,则表中有两个部分(一个代表 1,另一个代表 2)。为了规避这个问题并将它们分组在“#”下,我在存储核心数据时在以数字开头的姓氏前面加上了#。如果姓氏在表格渲染器中以 # 开头,则必须删除它。我相信这不是一个很好的方法。使用 fetched results controller 有没有更好的方法来处理这个问题?
    • 此外,# 部分出现在“A”上方的顶部(因为为获取的结果控制器指定的排序顺序)。但是默认的联系人应用程序在底部呈现#,这也是有效的。是否有可能通过获取结果控制器来实现这一点?还是我们应该手动编写管理各种数组的整个逻辑?我更喜欢获取结果控制器的内存处理和易用性。
    • 更新:我添加了另一个数字列来指示姓氏是否以字母开头并首先对其进行排序,然后在姓氏列上再次排序。 IE。产生适当结果的两个排序描述符(我也将 # 推到索引的底部)。到目前为止工作正常,但如果应用程序本地化为其他语言怎么办?如何检查名称的给定起始字母是否是标准字符集的一部分并且在索引中可用(假设索引已本地化)。
    【解决方案2】:

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

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

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

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

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

    似乎可行 - 是否有理由认为这是一个坏主意?或者我是否从 ios5 的新功能中受益?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多