NSFetchedResultsController v.s. UILocalizedIndexedCollation
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
- (NSString *)sectionIndexTitleForSectionName:(NSString *)sectionName
- (NSArray *)sectionIndexTitles
and then use the UILocalizedIndexedCollation to have a localized index and sections. But sadly this does not work and is not what is intended to be used :(
Has anyone been able to use a FRC with UILocalizedIndexedCollation or are we forced to use the manual sorting method mentioned in the example UITableView + UILocalizedIndexedCollation example (example code included where I got this working).
Using the following properties
@property (nonatomic, assign) UILocalizedIndexedCollation *collation;
@property (nonatomic, assign) NSMutableArray *collatedSections;
and the code:
- (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];
}
I would love to still be able to use the FRCDelegate protocol to be notified of updates. It seems like there is no good way making these two objects work together nicely.
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.