Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>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.</p> <p>So, here is the problem in general:</p> <p>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.</p> <p>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 ($, ?, ', …).</p> <p>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.</p> <p>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.</p> <p>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).</p> <p>Here is the solution that I came to:</p> <p>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.</p> <p>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.</p> <p>I noticed that FRC decides that "#" symbol is lower that any alphabet symbol opposite to LIC where "#" is highest.</p> <p>FRC's logic is pretty straightforward because "#" symbol in UTF-8 is U+0023. And latin capital "A" is U+0041, so 23 &lt; 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 <a href="http://www.utf8-chartable.de/unicode-utf8-table.pl" rel="nofollow">http://www.utf8-chartable.de/unicode-utf8-table.pl</a> that UTF-8 symbol is U+1000FF (). Of course there almost is no way that this symbol will occur in real life. Lets use U+100000 for clearness.</p> <p>Sort name update method looks something like this: </p> <pre><code>#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"]; } </code></pre> <p>FRC setup:</p> <pre><code>- (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; } </code></pre> <p>FRC delegate methods are default. TV delegate and data source methods:</p> <pre><code>- (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 &lt;NSFetchedResultsSectionInfo&gt; sectionInfo in sections) { if([[sectionInfo name] isEqualToString:indexTitle]) { fetchTitleIndex = [sections indexOfObject:sectionInfo]; break; } } return fetchTitleIndex; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { id &lt;NSFetchedResultsSectionInfo&gt; 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 &lt;NSFetchedResultsSectionInfo&gt; sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } </code></pre> <p>Thats it. So far works well. Maybe it will work for you.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload