I am trying to use a FRC with mixed language data and want to have a section index.
It seems like from the documentation you should be able to override the FRC\'s
Facing the same problem recently makes me searching web (stackoverflow firstly) for appropriate solution to make NSFetchedResultsController (FRC) and UILocalizedIndexedCollation (LIC) work together. Most find solutions wasn't good enough to fulfil all requirement. It is important to mention that we cannot use LIC to sort fetched objects in the way it needs course we will have huge performance lose and FRC wouldn't give use all advantage.
So, here is the problem in general:
1) We have DB with some kind of data that we want to fetch and display using FRC in a list (UITableView) with indexes (similar to Contacts.app). We need to pass object value key so FRC can make sort decision.
2) Even if we will add special field to our CoreData models for sections sorting and use FRC's section index titles we will not achieve desired result, course FRC only gives found indexes, but not complete alphabet. In bad addition to that we'll face problem with incorrect indexes displaying (not really sure why's that so, maybe some bug in FRC). In case of Russian alphabet, for example, there will be totally blank or "strange" symbols ($, ?, ', …).
3) If we will try to use LIC to display nice localized indexes we will face the problem of mapping data-based sections in FRC to complete localized alphabet "sections" in LIC.
4) After we decided to use LIC and somehow solve problem 3) we will notice that LIC will place "#" section to bottom (i.e. highest section index) but FRC will place "#"-like objects to top (i.e. lowest section index - 0). So will have complete sections displacement.
Taking all that into a count I decided to "trick" FRC without any big "hacking" but make it sort data in the way I need (move all objects that are from "#"-like section to bottom of list).
Here is the solution that I came to:
I add extension method to my NSManagedObject instance to prepare sort name that we will use in sort descriptor and section key path for FRC setup. No special moves needed except those one that will be described below.
Problem 4) occurs due to FRC's sorting algos (low-level SQL) that can be modified slightly: only by applying sort descriptors that are more your-data-dependant, predicates and using fixed predefined comparators that don't solve the problem.
I noticed that FRC decides that "#" symbol is lower that any alphabet symbol opposite to LIC where "#" is highest.
FRC's logic is pretty straightforward because "#" symbol in UTF-8 is U+0023. And latin capital "A" is U+0041, so 23 < 41. In order to make FRC would place "#"-like object to highest index section we need to pass highest UTF-8 symbol. In order to this source http://www.utf8-chartable.de/unicode-utf8-table.pl that UTF-8 symbol is U+1000FF (
Since you cannot sort on a transient property, the solution I implemented is...
Create a string attribute called "sectionKey" for each sortable attribute within each entity in your Core Data model. The sectionKey attribute will be a calculated value derived from a base attribute (e.g., a name or title attribute). It must be persisted because (currently) a transient property cannot be used in a sort descriptor for a fetch request. Enable indexing on each sectionKey and base attribute for which sorting will be offered. In order to apply this update to an existing app, you will need to perform a lightweight migration, and also include a routine to update pre-existing databases.
If you are seeding data (e.g., to populate new installs with a standard set of data, or to create localized SQLite databases for each target language, of which one will be copied over on initial launch), in that code, calculate and update each entity's sectionKey attribute(s). Opinions vary as to the "best" approach to seeding data, however it's worth noting that a handful of plist files for each language (which will typically range from a few bytes to 20k, even for a list comprised of several hundred values) will leave a much smaller overall footprint than an individual SQLite database for each language (which start at about 20k each). On a side note, Microsoft Excel for Mac can be configured to provide localized sorting of lists by enabling the language features (3).
In the fetched results controller constructor, sort on the sectionKey and base attribute, and pass the sectionKey for the section name key path.
Add the calculation logic to update the sectionKey attribute(s) in all add or edit user inputs, for example, in textFieldDidEndEditing:.
That's it! No manual partitioning of fetched objects into an array of arrays. NSFetchedResultsController will do the localized collation for you. For example, in the case of Chinese (Simplified), the fetched objects will be indexed by phonetic pronunciation (4).
(1) From Apple IOS Developer Library > Internationalization Programming Topics > Internationalization and Localization. (2) 3_SimpleIndexedTableView of the TableViewSuite. (3) How to enable Chinese language features in Microsoft Office for Mac. (4) The Chinese language is commonly sorted by either stroke count or phonetic pronunciation.
Brent, my solution is based on FRC and I get sectioning from the fetch specifying a transient attribute on my model object that returns the section name for the object. I use UIlocalizedIndexedCollation only in the implementation of the attribute getter then I rely on the FRC implementation on the table view controller. Of course I use localizedCaseInsensitiveCompare as sorting selector on the fetch.
- (NSString *)sectionInitial {
NSInteger idx = [[UILocalizedIndexedCollation currentCollation] sectionForObject:self collationStringSelector:@selector(localeName)];
NSString *collRet = [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:idx];
return collRet;
}
The only drawback I have is that i can't have the # section at the end because I don't change the sorting from the DB. Everything else works well.
I found a easy way to solve this!
Just replace "#" to "^" in your core data so that the sections for your tableview will be "A-Z^". While unicode of '#' is smaller than 'A', '^''s is just the opposite. So it's not difficult for you to predict '^' will follow Z in your sections.
Then, you should replace your fetched results controller's sections. just by this couple of lines code:
- (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];
}