NSFetchedResultsController v.s. UILocalizedIndexedCollat​​ion

时间:2011-08-26 04:09:48

标签: ios core-data localization nsfetchedresultscontroller uilocalizedcollation

我正在尝试使用带有混合语言数据的FRC,并希望有一个部分索引。

似乎从文档中你应该能够覆盖FRC的

- (NSString *)sectionIndexTitleForSectionName:(NSString *)sectionName
- (NSArray *)sectionIndexTitles

然后使用UILocalizedIndexedCollat​​ion来获得本地化的索引和部分。但遗憾的是,这不起作用,也不是意图使用的:(

有没有人能够使用带有UILocalizedIndexedCollat​​ion的FRC,或者我们被迫使用示例UITableView + UILocalizedIndexedCollat​​ion示例中提到的手动排序方法(示例代码包含在我工作的地方)。

使用以下属性

@property (nonatomic, assign) UILocalizedIndexedCollation *collation;
@property (nonatomic, assign) NSMutableArray *collatedSections;

和代码:

- (UILocalizedIndexedCollation *)collation
{
    if(collation == nil)
    {
        collation = [UILocalizedIndexedCollation currentCollation];
    }

    return collation;
}

- (NSArray *)collatedSections
{
    if(_collatedSections == nil)
    {
        int sectionTitlesCount = [[self.collation sectionTitles] count];

        NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
        collatedSections = newSectionsArray;
        NSMutableArray *sectionsCArray[sectionTitlesCount];

        // Set up the sections array: elements are mutable arrays that will contain the time zones for that section.
        for(int index = 0; index < sectionTitlesCount; index++) 
        {
            NSMutableArray *array = [[NSMutableArray alloc] init];
            [newSectionsArray addObject:array];
            sectionsCArray[index] = array;
            [array release];
        }


        for(NSManagedObject *call in self.fetchedResultsController.fetchedObjects)
        {
            int section = [collation sectionForObject:call collationStringSelector:NSSelectorFromString(name)];
            [sectionsCArray[section] addObject:call];
        }

        NSArray *sortDescriptors = self.fetchedResultsController.fetchRequest.sortDescriptors;
        for(int index = 0; index < sectionTitlesCount; index++) 
        {
            [newSectionsArray replaceObjectAtIndex:index withObject:[sectionsCArray[index] sortedArrayUsingDescriptors:sortDescriptors]];
        }
    }
    return [[collatedSections retain] autorelease];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
    // The number of sections is the same as the number of titles in the collation.
    return [[self.collation sectionTitles] count];
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{
    // The number of time zones in the section is the count of the array associated with the section in the sections array.
    return [[self.collatedSections objectAtIndex:section] count];
}


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 
{
    if([[self.collatedSections objectAtIndex:section] count])
        return [[self.collation sectionTitles] objectAtIndex:section];
    return nil;
}


- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [self.collation sectionIndexTitles];
}


- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [self.collation sectionForSectionIndexTitleAtIndex:index];
}

我希望仍能使用FRCDelegate协议获得更新通知。似乎没有什么好方法可以让这两个对象很好地协同工作。

4 个答案:

答案 0 :(得分:6)

由于您无法对瞬态属性进行排序,因此我实施的解决方案是......

  1. 为Core Data模型中每个实体中的每个可排序属性创建一个名为“sectionKey”的字符串属性。 sectionKey属性将是从基本属性(例如,名称或标题属性)导出的计算值。它必须是持久化的,因为(当前)瞬态属性不能用于获取请求的排序描述符。在每个sectionKey和base属性上启用索引,以便为其提供排序。要将此更新应用于现有应用程序,您需要执行轻量级迁移,并且还包括更新预先存在的数据库的例程。

  2. 如果您正在播种数据(例如,使用标准数据集填充新安装,或者为每种目标语言创建本地化SQLite数据库,其中一个将在初始启动时被复制),代码,计算和更新每个实体的sectionKey属性。关于播种数据的“最佳”方法的意见不一,但是值得注意的是,每种语言的少数plist文件(通常范围从几个字节到20k,即使是由几百个值组成的列表)也会离开与每种语言的单个SQLite数据库相比,总体占用空间要小得多(每个语言的起始速度约为20k)。另外,Microsoft Excel for Mac可以配置为通过启用语言功能(3)来提供列表的本地化排序。

  3. 在获取的结果控制器构造函数中,对sectionKey和base属性进行排序,并为section name键路径传递sectionKey。

  4. 添加计算逻辑以更新所有添加或编辑用户输入中的sectionKey属性,例如,在textFieldDidEndEditing中:。

  5. 就是这样!没有手动将获取的对象分区为数组数组。 NSFetchedResultsController将为您执行本地化排序规则。例如,在中文(简体)的情况下,获取的对象将通过语音发音(4)进行索引。

    (1)来自Apple IOS开发人员库&gt;国际化编程主题&gt; Internationalization and Localization。 (2)TableViewSuite的3_SimpleIndexedTableView。 (3)How to enable Chinese language features in Microsoft Office for Mac。 (4)汉语通常按笔画数或语音发音排序。

答案 1 :(得分:3)

Brent,我的解决方案基于FRC,我从fetch中获取分区,在我的模型对象上指定一个transient属性,返回对象的section名称。我只在getter属性的实现中使用UIlocalizedIndexedCollat​​ion然后我依赖于表视图控制器上的FRC实现。当然,我使用localizedCaseInsensitiveCompare作为fetch的排序选择器。

- (NSString *)sectionInitial {

    NSInteger idx = [[UILocalizedIndexedCollation currentCollation] sectionForObject:self     collationStringSelector:@selector(localeName)];
    NSString *collRet = [[[UILocalizedIndexedCollation currentCollation] sectionTitles]     objectAtIndex:idx];

    return collRet;
}

我唯一的缺点是我最后不能拥有#部分,因为我没有改变数据库中的排序。其他一切都运作良好。

答案 2 :(得分:2)

最近面临同样的问题让我搜索web(首先是stackoverflow)以获得适当的解决方案,以使NSFetchedResultsController(FRC)和UILocalizedIndexedCollat​​ion(LIC)协同工作。大多数人发现解决方案不足以满足所有要求。值得一提的是,我们不能使用LIC按照需要的方式对获取的对象进行排序,因此我们将失去巨大的性能,而FRC不会充分利用它。

所以,这是一般的问题:

1)我们有一些DB,我们想要在带有索引的列表(UITableView)中使用FRC获取和显示某些数据(类似于Contacts.app)。我们需要传递对象值键,以便FRC可以做出排序决定。

2)即使我们将为我们的CoreData模型添加特殊字段以进行部分排序并使用FRC的部分索引标题,我们也无法获得所需的结果,当然FRC仅提供找到的索引,但不是完整的字母表。除此之外我们还会遇到错误索引显示的问题(不确定为什么会这样,也许是FRC中的一些错误)。例如,在俄语字母的情况下,将会有完全空白或“奇怪”的符号($,?,',...)。

3)如果我们尝试使用LIC来显示好的本地化索引,我们将面临在FRC中映射基于数据的部分以在LIC中完成本地化字母“部分”的问题。

4)在我们决定使用LIC并以某种方式解决问题之后3)我们会注意到LIC会将“#”部分放在底部(即最高部分索引),但FRC会将“#” - 类似对象放到顶部(即最低部分指数 - 0)。因此将有完整的部分位移。

考虑到所有这些,我决定“欺骗”FRC而没有任何大的“黑客攻击”但是让它按照我需要的方式对数据进行排序(移动所有来自“#”的对象 - 如部分到列表底部)

以下是我的解决方案:

我将扩展方法添加到我的NSManagedObject实例,以准备我们将在排序描述符和FRC设置的部分键路径中使用的排序名称。除了将在下面描述的那些之外,不需要特殊的移动。

问题4)由于FRC的排序算法(低级SQL)可以稍微修改而发生:仅通过应用更依赖于数据的排序描述符,谓词并使用无法解决的固定预定义比较器问题

我注意到FRC决定“#”符号低于任何与LIC相对的字母符号,其中“#”最高。

FRC的逻辑非常简单,因为UTF-8中的“#”符号是U + 0023。拉丁语大写“A”是​​U + 0041,所以23&lt; 41.为了使FRC将“#” - 像对象放到最高索引部分,我们需要传递最高的UTF-8符号。为了这个源http://www.utf8-chartable.de/unicode-utf8-table.pl,UTF-8符号是U + 1000FF()。当然,这个符号几乎没有办法在现实生活中出现。让我们使用U + 100000清晰。

排序名称更新方法如下所示:

#define UT8_MAX @"\U00100000"

- (void)updateSortName
{
    NSMutableString *prSortName = [NSMutableString stringWithString:[self dataDependantSortName]]; // for sort descriptors

    NSString *prSectionIdentifier = [[prSortName substringToIndex:1] uppercaseString]; // section keypath

    UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];

    NSUInteger sectionIndex = [collation sectionForObject:prSectionIdentifier collationStringSelector:@selector(stringValue)]; // stringValue is NSString category method that returns [NSString stringWithString:self]

    if(sectionIndex == [[collation sectionTitles] count] - 1) // last section tile '#'
    {
        prSectionIdentifier = UT8_MAX;
    }
    else
    {
        prSectionIdentifier = [collation sectionTitles][sectionIndex];
    }

    [prSortName replaceCharactersInRange:NSMakeRange(0, 1) withString:prSectionIdentifier];

//    sortName, sectionIdentifier - non-transient string attributes in CoreData model

    [self willChangeValueForKey:@"sortName"];
    [self setPrimitiveValue:prSortName forKey:@"sortName"];
    [self didChangeValueForKey:@"sortName"];

    [self willChangeValueForKey:@"sectionIdentifier"];
    [self setPrimitiveValue:prSectionIdentifier forKey:@"sectionIdentifier"];
    [self didChangeValueForKey:@"sectionIdentifier"];
}

FRC设置:

- (void)setupFRC
{
    NSEntityDescription *entityDescription =
    [NSEntityDescription entityForName:@"entity"
                inManagedObjectContext:self.moc];

    NSSortDescriptor *sortNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sortName" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]; // or any selector you need
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortNameDescriptor, nil];

    NSFetchRequest *fetchRequest = [NSFetchRequest new];
    [fetchRequest setEntity:entityDescription];
    [fetchRequest setFetchBatchSize:BATCH_SIZE];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *fetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.moc
                                          sectionNameKeyPath:@"sectionIdentifier"
                                                   cacheName:nil];
    self.fetchedResultsController = fetchedResultsController;
}

FRC委托方法是默认的。电视代表和数据源方法:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [[self localizedIndexedCollation] sectionTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    NSString *indexTitle = [title isEqualToString:@"#"] ? UT8_MAX : title;
    NSInteger fetchTitleIndex = NSNotFound;

    NSArray *sections = [self.fetchedResultsController sections];
    for (id <NSFetchedResultsSectionInfo> sectionInfo in sections)
    {
        if([[sectionInfo name] isEqualToString:indexTitle])
        {
            fetchTitleIndex = [sections indexOfObject:sectionInfo];
            break;
        }
    }

    return fetchTitleIndex;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    NSString *fetchTitle = [sectionInfo name];

    NSInteger collationTitleIndex = [[self localizedIndexedCollation] sectionForObject:fetchTitle
                                                               collationStringSelector:@selector(stringValue)];
    return [[[self localizedIndexedCollation] sectionTitles] objectAtIndex:collationTitleIndex];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

多数民众赞成。到目前为止效果很好。也许它会对你有用。

答案 3 :(得分:2)

我找到了解决这个问题的简单方法!

只需更换&#34;#&#34;到&#34; ^&#34;在您的核心数据中,以便您的tableview的部分将是&#34; A-Z ^&#34;。虽然&#39;#&#39;的unicode小于&#39; A&#39; ^&#39;&#39;&#39;&nbsp;&#39;&#39;&#39;&#39;&#39;&#39;所以你预测&#39; ^&#39;并不困难。将在你的章节中跟随Z.

然后,您应该替换获取的结果控制器部分。只需这几行代码:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{

    NSMutableArray *array = [[NSMutableArray alloc] initWithArray:[self.frc sectionIndexTitles]];

    // If "^" is in the section, replace it to "#"
    if ( [[array lastObject] isEqualToString:@"^"])
    {
        [array setObject:@"#" atIndexedSubscript:[array count]-1];
        return array;
    }
    // If "#" is not in the section
    return [self.frc sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title
               atIndex:(NSInteger)index
{
    if ([title isEqualToString:@"#"]) {
        return [self.frc sectionForSectionIndexTitle:@"^" atIndex:index];
    }
    return [self.frc sectionForSectionIndexTitle:title atIndex:index];
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if ([[[self.frc sectionIndexTitles] objectAtIndex:section] isEqualToString:@"^"]) {
        return @"#";
    }
    return [[self.frc sectionIndexTitles] objectAtIndex:section];
}