NSString localizedCompare:给定更长字符串

时间:2017-06-28 18:07:35

标签: ios cocoa core-data unicode localization

我们尝试使用NSFetchedResultsController返回人名,并使用UITableView按排序顺序填充localizedCompare:。我们还尝试在UI中提供节索引(每个节的第一个字符的右列)。我们在我们的实体上为NSFetchedResultsController提供了一个选择器,它提供了每个实体应该属于的部分(具体地说,是该人名的第一个字符,大写)。

在处理使用Unicode代码点的人名时,我们遇到了一个问题。 NSFetchedResultsController抱怨实体未按部分排序。

具体做法是:

reason=The fetched object at index 103 has an out of order section name 'Ø. Objects must be sorted by section name'}, {
reason = "The fetched object at index 103 has an out of order section name '\U00d8. Objects must be sorted by section name'";

问题似乎是localizedCompare: 返回的比较值对于整个“单词”与主要字符不同。

以下测试传递虽然我希望(“Ø”和“O”)与(“Østerhus”和“Osypowicz”)之间的比较结果一致。

- (void)testLocalizedSortOrder300
{
    NSString *str1 = @"Osowski";
    NSString *str2 = @"Østerhus";
    NSString *str3 = @"Osypowicz";

    NSString *letter1 = @"O";
    NSString *letter2 = @"Ø";

    //localizedCompare:

    //"Osowski" < "Østerhus"
    NSComparisonResult res = [str1 localizedCompare:str2];
    XCTAssertTrue(res == NSOrderedAscending, @"(localizedCompare:) Expected '%@' and '%@' to be NSOrderedAscending, but got %@", str1, str2, res == NSOrderedSame ? @"NSOrderedSame" : @"NSOrderedDescending");

    //"Østerhus" < "Osypowicz"
    res = [str2 localizedCompare:str3];
    XCTAssertTrue(res == NSOrderedAscending, @"(localizedCompare:) Expected '%@' and '%@' to be NSOrderedAscending, but got %@", str2, str3, res == NSOrderedSame ? @"NSOrderedSame" : @"NSOrderedDescending");

    //"O" < "Ø"
    res = [letter1 localizedCompare:letter2];
    XCTAssertTrue(res == NSOrderedAscending, @"(localizedCompare:) Expected '%@' and '%@' to be NSOrderedAscending, but got %@", letter1, letter2, res == NSOrderedSame ? @"NSOrderedSame" : @"NSOrderedDescending");
}

所以,问题最终是,给定一个使用Unicode代码点的人名(或任何其他字符串),我们如何正确地(以本地化的方式)返回一个部分名称,该部分名称将与排序顺序相对应,如下所示: localizedCompare:

此外,localizedCompare:显然将“Ø”和“O”视为NSOrderedSame,后面跟着其他字符会发生什么?

3 个答案:

答案 0 :(得分:0)

我希望localizedCompare:正在使用导致此行为的NSStringCompareOptions标志的特定组合。 https://developer.apple.com/documentation/foundation/nsstringcompareoptions?preferredLanguage=occ

您可以使用compare:options:并启用NSDiacriticInsensitiveSearch来获得所需的结果。

为了生成节索引,最好先删除所有扩展字符的值,然后取第一个字母。类似的东西:

[[str1 stringByFoldingWithOptions:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch] substringToIndex:1]

这样,以“Édward”等重音字母开头的名字将在您获取该部分的第一个字母之前转换为“Edward”。

答案 1 :(得分:0)

是的,去过那里。我找到的唯一解决方案是为搜索创建第二个字段,以简化字符(不记得手边的方法)并将其存储为用于搜索的第二个字段。不是超级优雅,但它的工作。

答案 2 :(得分:0)

最终解决此问题的方法是将规范化的部分名称存储在数据库中。

@MartinR建议SO帖子引导我到https://stackoverflow.com/a/13292767/397210谈论这种方法,并且是解决它的关键“啊哈”时刻。

虽然这并不能解释localizedCompare:显然将“Ø”和“O”视为NSOrderedSame的愚蠢行为,然后是其他字符,恕我直言,这是一个更强大,更完整的解决方案,适用于所有Unicode代码点,在我们的测试中。

具体来说,方法是:

  1. 在您的实体上创建(或利用现有的)字段,以接收实体的规范化部分名称(让我们称之为sectionName)。
  2. 最初使用规范化的部分名称*填充此字段(sectionName),并根据需要(例如,当人名更改时)。
  3. 将此部分名称字段(sectionName)用于sectionNameKeyPath NSFetchedResultsController
  4. -initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:
  5. 对于传递给NSFetchedResultsController的获取请求所使用的排序描述符,请务必先按节名称排序,然后按如何对节的内容进行排序(例如,人名),付款注意使用本地化版本的比较选择器。 e.g:

    [NSSortDescriptor sortDescriptorWithKey:@"sectionName" ascending:YES selector:@selector(localizedStandardCompare:)],
    [NSSortDescriptor sortDescriptorWithKey:@"personName" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]
    
  6. 测试。

  7. *标准化部分名称

    我们需要注意在处理unicode时假设第一个“字符”是什么。 “字符”可以由多个字符组成。 见https://www.objc.io/issues/9-strings/unicode/ 还有Compare arabic strings with special characters ios

    这是我用来生成规范化部分名称的方向:

        NSString *decomposedString = name.decomposedStringWithCanonicalMapping;
        NSRange firstCharRange = [decomposedString rangeOfComposedCharacterSequenceAtIndex:0];
        NSString *firstChar = [decomposedString substringWithRange:firstCharRange];
        retVal = [firstChar localizedUppercaseString];
    

    希望这种方法对其他人来说是明确和有用的,并且感谢所有人的帮助。