【问题标题】:How to use the first character as a section name如何使用第一个字符作为部分名称
【发布时间】:2010-12-16 23:59:42
【问题描述】:

我正在将 Core Data 用于表格视图,并且我想将每个结果的第一个字母用作部分标题(这样我就可以在侧面获得部分索引)。有没有办法用关键路径做到这一点?如下所示,我使用name.firstLetter 作为sectionNameKeyPath(不幸的是这不起作用)。

我是否必须手动抓取每个结果的第一个字母并像这样创建我的部分?放入一个新属性以仅保留第一个字母并将其用作sectionNameKeyPath 更好吗?

NSFetchedResultsController *aFetchedResultsController = 
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
            managedObjectContext:managedObjectContext
            sectionNameKeyPath:@"name.firstLetter"
            cacheName:@"Root"];

谢谢。

**编辑:** 我不确定它是否有区别,但我的结果是日语,按片假名排序。我想用这些片假名作为章节索引。

【问题讨论】:

    标签: iphone objective-c cocoa-touch uitableview core-data


    【解决方案1】:

    这是 obj-c 的简单解决方案,几年和一种语言晚了。它可以在我的一个项目中快速运行。

    首先我在 NSString 上创建了一个类别,将文件命名为 NSString+Indexing。我写了一个返回字符串首字母的方法

    @implementation NSString (Indexing)
    
    - (NSString *)stringGroupByFirstInitial {
        if (!self.length || self.length == 1)
            return self;
        return [self substringToIndex:1];
    }
    

    然后我在获取结果控制器的定义中使用了那个方法,如下

    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"name.stringGroupByFirstInitial"
                                                                                   cacheName:nil];
    

    上面的代码与两个 stringIndex 方法一起工作,不需要弄乱瞬态属性或存储一个单独的属性,它只保存核心数据中的第一个字母

    但是,当我尝试对 Swift 执行相同操作时,它会引发异常,因为它不喜欢将函数作为部分键路径(或计算的字符串属性 - 我也尝试过)如果那里有人知道如何实现我非常想知道 Swift 中的相同内容。

    【讨论】:

      【解决方案2】:

      请参阅我对类似问题 here 的回答,其中我描述了如何创建持久化的本地化 sectionKey 索引(因为您无法对 NSFetchedResultsController 中的瞬态属性进行排序)。

      【讨论】:

        【解决方案3】:

        优雅的方法是让“firstLetter”成为一个瞬态属性,但在实践中这很慢。

        它很慢,因为要计算瞬态属性,需要将整个对象错误地放入内存中。如果你有很多记录,它会非常非常慢。

        快速但不优雅的方法是创建一个非瞬态“firstLetter”属性,每次设置“name”属性时都会更新该属性。有几种方法可以做到这一点:覆盖“setName:”评估器、覆盖“willSave”、KVO。

        【讨论】:

        • 您能确认这一点吗?我无法在我的项目中发现瞬态属性和持久属性之间的区别。
        • 我在对@gerry3 答案的评论中质疑“优雅”的方式。而“不雅”比它看起来更“不雅”。索引应该来自UILocalizedIndexedCollation。因此,您应该重置每个更改区域设置怀疑的条目,以将当前排序规则与您的第一个字母匹配。
        【解决方案4】:

        我使用NSFetchedResultsController v.s. UILocalizedIndexedCollation 问题中提到的 UILocalizedIndexCollat​​ion 解决了这个问题

        【讨论】:

        • 这确实是解决它的唯一方法。 UILocalizedIndexCollat​​ion 有很多内置行为,而其他答案中建议的大写FirstLetterOfName 代码甚至都没有接近。举一个例子,UILocalizedIndexCollat​​ion 正确地将日语半角和全角片假名折叠到正确的部分,部分名称由正确的平假名字符表示 - 它对世界上几乎所有其他语言也做了正确的事情。
        • 我知道这已经晚了十年,但@JosephH / Brent Priddy 你介意发布你如何让它工作的代码吗?
        【解决方案5】:

        您应该只传递“名称”作为 sectionNameKeyPath。看到这个answer 到问题“Core Data backed UITableView with indexing”。

        更新
        该解决方案仅在您只关心拥有快速索引标题滚动器时才有效。在这种情况下,您将不会显示节标题。请参阅下面的示例代码。

        否则,我同意 refulgentis 的观点,即瞬态属性是最佳解决方案。另外,在创建 NSFetchedResultsController 时,sectionNameKeyPath 有这个限制:

        如果此密钥路径与 由第一个排序指定的 fetchRequest 中的描述符,它们必须 生成相同的相对顺序。 例如,第一个排序描述符 在 fetchRequest 中可能会指定密钥 对于持久属性; sectionNameKeyPath 可能指定一个键 对于衍生自的瞬态属性 持久属性。

        使用 NSFetchedResultsController 的样板 UITableViewDataSource 实现:

        - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
            return [[fetchedResultsController sections] count];
        }
        
        - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
            return [fetchedResultsController sectionIndexTitles];
        }
        
        - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
            return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
        }
        
        - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
            id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
            return [sectionInfo numberOfObjects];
        }
        
        // Don't implement this since each "name" is its own section:
        //- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
        //    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
        //    return [sectionInfo name];
        //}
        

        更新 2

        对于新的 'uppercaseFirstLetterOfName' 瞬态属性,将新的字符串属性添加到模型中的适用实体并选中“瞬态”框。

        有几种方法可以实现 getter。如果您正在生成/创建子类,则可以将其添加到子类的实现 (.m) 文件中。

        否则,您可以在 NSManagedObject 上创建一个类别(我将其放在视图控制器的实现文件的顶部,但您可以将其拆分为适当的头文件和它自己的实现文件):

        @interface NSManagedObject (FirstLetter)
        - (NSString *)uppercaseFirstLetterOfName;
        @end
        
        @implementation NSManagedObject (FirstLetter)
        - (NSString *)uppercaseFirstLetterOfName {
            [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
            NSString *aString = [[self valueForKey:@"name"] uppercaseString];
        
            // support UTF-16:
            NSString *stringToReturn = [aString substringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:0]];
        
            // OR no UTF-16 support:
            //NSString *stringToReturn = [aString substringToIndex:1];
        
            [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
            return stringToReturn;
        }
        @end
        

        另外,在这个版本中,不要忘记将 'uppercaseFirstLetterOfName' 作为 sectionNameKeyPath 传递:

        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext
        sectionNameKeyPath:@"uppercaseFirstLetterOfName" // this key defines the sections
        cacheName:@"Root"];
        

        并且,在 UITableViewDataSource 实现中取消注释 tableView:titleForHeaderInSection:

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

        【讨论】:

        • 我尝试将名称作为sectionNameKeyPath 输入,但最终返回的每个名称都有一个部分。我得到的部分与名称一样多,每个部分只有一个条目(名称)。
        • 您可能没有正确设置。再读一遍答案。它像宣传的那样工作。
        • 我在完成这项工作时遇到了最大的麻烦。我的解决方案是使用[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)] 而不是[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES],因为我的一个对象以小写字符开头。
        • 我认为不需要 willAccessValueForKey 和 didAccessValueForKey 调用。您是在告诉 CD,您正在访问您正在生成的价值。没必要。
        • 这不是完美的解决方案!文档:The fetch request must have at least one sort descriptor. If the controller generates sections, the first sort descriptor in the array is used to group the objects into sections; its key must either be the same as sectionNameKeyPath or the relative ordering using its key must match that using sectionNameKeyPath.。这两个条件都不满足。您无法在瞬态属性上构建排序描述符(它总是对我崩溃),相对排序也不匹配(如果有整个 UTF8 集,caseInsensitiveCompare: 不足以匹配)。
        【解决方案6】:

        可能有更优雅的方法可以做到这一点,但我最近遇到了同样的问题并想出了这个解决方案。

        首先,我在要索引的对象上定义了一个名为 firstLetterOfName 的瞬态属性,并将 getter 写入对象的 .m 文件中。 例如

        - (NSString *)uppercaseFirstLetterOfName {
            [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
            NSString *stringToReturn = [[self.name uppercaseString] substringToIndex:1];
            [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
            return stringToReturn;
        }
        

        接下来,我设置我的获取请求/实体以使用此属性。

        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Object" inManagedObjectContext:dataContext];
        [request setEntity:entity];
        [NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
        NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
        [request setSortDescriptors:sortDescriptors];
        

        旁注,无所谓:小心 NSFetchedResultsController - 恕我直言,它还没有完全成熟,除了文档中列出的简单案例之外的任何情况,你可能会更好地使用“老式”方式。

        【讨论】:

        • +1 瞬态属性是显示节标题时的最佳解决方案。
        • 当心substringToIndex:!如果字符串是 UTF-16 字符串,则只会抓取第一个字符的前半部分。正确的做法是[aString subStringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:1]]
        • 感谢您的回答。我不明白你在哪里调用 uppercaseFirstLetterOfName 方法。您应该调用它而不是 caseInsensitiveCompare 还是有其他方式调用它?
        • 我将有关瞬态属性的详细信息作为更新 2 添加到我的答案中。
        猜你喜欢
        • 1970-01-01
        • 2017-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多