14

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

从文档看来,您应该能够覆盖 FRC

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

然后使用 UILocalizedIndexedCollat​​ion 有一个本地化的索引和部分。但遗憾的是,这不起作用,也不是打算使用的:(

是否有人能够将 FRC 与 UILocalizedIndexedCollat​​ion 一起使用,或者我们是否被迫使用示例 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

4 回答 4

6

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

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

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

  3. 在获取的结果控制器构造函数中,对 sectionKey 和 base 属性进行排序,并将 sectionKey 传递给部分名称键路径。

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

而已!无需手动将获取的对象划分为数组数组。NSFetchedResultsController 将为您进行本地化排序。例如,在中文(简体)的情况下,获取的对象将通过拼音(4)进行索引。

(1) 来自 Apple IOS Developer Library > Internationalization Programming Topics > Internationalization and Localization。(2) TableViewSuite的 3_SimpleIndexedTableView 。(3)如何在 Microsoft Office for Mac 中启用中文功能。(4) 汉语通常按笔划数或音标排序。

于 2012-11-08T16:03:24.547 回答
3

布伦特,我的解决方案是基于 FRC 的,我从 fetch 中获取切片,指定模型对象上的瞬态属性,该属性返回对象的部分名称。我只在属性 getter 的实现中使用 UIlocalizedIndexedCollat​​ion 然后我依赖表视图控制器上的 FRC 实现。当然,我使用localizedCaseInsensitiveCompare 作为获取的排序选择器。

- (NSString *)sectionInitial {

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

    return collRet;
}

我唯一的缺点是我不能在末尾有 # 部分,因为我没有更改数据库的排序。其他一切都很好。

于 2012-02-16T17:38:51.677 回答
2

最近面临同样的问题让我在网上搜索(首先是stackoverflow)寻找合适的解决方案,以使NSFetchedResultsController(FRC)和UILocalizedIndexedCollat​​ion(LIC)一起工作。大多数找到的解决方案都不足以满足所有要求。值得一提的是,我们不能使用 LIC 以它需要的方式对获取的对象进行排序,当然我们将有巨大的性能损失,而且 FRC 不会充分利用所有优势。

所以,这里是一般的问题:

1)我们有某种数据的数据库,我们希望使用 FRC 在带有索引的列表(UITableView)中获取和显示(类似于 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 < 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 委托方法是默认的。TV 委托和数据源方法:

- (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];
}

而已。到目前为止效果很好。也许它会为你工作。

于 2013-03-23T14:35:58.733 回答
2

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

只需将核心数据中的“#”替换为“^”,以便您的 tableview 的部分将是“AZ^”。虽然 '#' 的 unicode 小于 'A',但 '^' 正好相反。因此,您不难预测 '^' 将在您的部分中跟随 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];
}
于 2014-11-04T11:08:00.183 回答