NSFetchedResultsController vs UILocalizedIndexedCollation

Estoy tratando de usar un FRC con datos de lenguaje mixto y quiero tener un índice de sección.

Parece que a partir de la documentación debe poder anular los FRC

- (NSString *)sectionIndexTitleForSectionName:(NSString *)sectionName - (NSArray *)sectionIndexTitles 

y luego use UILocalizedIndexedCollation para tener un índice localizado y secciones. Pero lamentablemente esto no funciona y no es lo que se pretende utilizar 🙁

¿Alguien ha podido usar un FRC con UILocalizedIndexedCollation o estamos obligados a utilizar el método de ordenación manual mencionado en el ejemplo ejemplo de UITableView + UILocalizedIndexedCollation (código de ejemplo incluido donde obtuve este funcionamiento).

Usando las siguientes propiedades

 @property (nonatomic, assign) UILocalizedIndexedCollation *collation; @property (nonatomic, assign) NSMutableArray *collatedSections; 

y el código:

 - (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]; } 

Me encantaría poder seguir utilizando el protocolo FRCDelegate para recibir notificaciones de actualizaciones. Parece que no hay una buena manera de hacer que estos dos objetos funcionen juntos muy bien.

Como no puede ordenar en una propiedad transitoria, la solución que implementé es …

  1. Cree un atributo de cadena llamado “sectionKey” para cada atributo ordenable dentro de cada entidad en su modelo de Datos Core. El atributo sectionKey será un valor calculado derivado de un atributo base (por ejemplo, un nombre o atributo de título). Debe persistir porque (actualmente) no se puede usar una propiedad transitoria en un descriptor de clasificación para una solicitud de recuperación. Habilite la indexación en cada sección Clave y atributo base para los que se ofrecerá la clasificación. Para aplicar esta actualización a una aplicación existente, deberá realizar una migración ligera y también incluir una rutina para actualizar bases de datos preexistentes.

  2. Si está recostackndo datos (por ejemplo, para completar nuevas instalaciones con un conjunto de datos estándar, o para crear bases de datos SQLite localizadas para cada idioma de destino, de las cuales se copiará en el inicio), en ese código, calcule y actualice cada atributo (s) clave de la sección de la entidad. Las opiniones varían en cuanto al “mejor” enfoque para la siembra de datos, sin embargo vale la pena señalar que un puñado de archivos plist para cada idioma (que generalmente oscilará entre unos pocos bytes y 20k, incluso para una lista compuesta por varios cientos de valores) saldrá una huella global mucho más pequeña que una base de datos SQLite individual para cada idioma (que comienza en aproximadamente 20k cada uno). En una nota lateral, Microsoft Excel para Mac se puede configurar para proporcionar clasificación localizada de listas habilitando las características de idioma (3).

  3. En el constructor del controlador de resultados obtenidos, ordena los atributos sectionKey y base, y pasa la sectionKey para la ruta de la clave del nombre de la sección.

  4. Agregue la lógica de cálculo para actualizar los atributos sectionKey en todas las entradas de usuario add o edit, por ejemplo, en textFieldDidEndEditing :.

¡Eso es! Sin partición manual de objetos recuperados en una matriz de matrices. NSFetchedResultsController hará la intercalación localizada para usted. Por ejemplo, en el caso de chino (simplificado), los objetos obtenidos se indexarán por pronunciación fonética (4).

(1) De Apple IOS Developer Library> Temas de progtwigción de internacionalización > Internacionalización y localización . (2) 3_SimpleIndexedTableView de TableViewSuite . (3) Cómo habilitar las características del idioma chino en Microsoft Office para Mac . (4) El idioma chino generalmente se ordena por recuento de trazos o pronunciación fonética.

Brent, mi solución está basada en FRC y obtengo un corte de la búsqueda que especifica un atributo transitorio en mi objeto modelo que devuelve el nombre de la sección del objeto. Utilizo UIlocalizedIndexedCollation solo en la implementación del atributo getter luego confío en la implementación de FRC en el controlador de vista de tabla. Por supuesto, utilizo localizedCaseInsensitiveCompare como selector de clasificación en la búsqueda.

 - (NSString *)sectionInitial { NSInteger idx = [[UILocalizedIndexedCollation currentCollation] sectionForObject:self collationStringSelector:@selector(localeName)]; NSString *collRet = [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:idx]; return collRet; } 

El único inconveniente que tengo es que no puedo tener la sección # al final porque no cambio la clasificación de la base de datos. Todo lo demás funciona bien

Hacer frente al mismo problema recientemente me hace buscar en la web (stackoverflow en primer lugar) para obtener la solución adecuada para hacer que NSFetchedResultsController (FRC) y UILocalizedIndexedCollation (LIC) funcionen juntos. La mayoría de las soluciones de búsqueda no fueron lo suficientemente buenas como para cumplir todos los requisitos. Es importante mencionar que no podemos usar LIC para clasificar los objetos captados de la manera en que lo necesitamos. Tendremos un gran rendimiento que perder y FRC no daría todas las ventajas.

Entonces, aquí está el problema en general:

1) Tenemos DB con algún tipo de datos que queremos buscar y visualizar usando FRC en una lista (UITableView) con índices (similar a Contacts.app). Necesitamos pasar la clave de valor del objeto para que FRC pueda tomar una decisión de clasificación.

2) Incluso si añadimos un campo especial a nuestros modelos CoreData para la clasificación de secciones y usamos los títulos de índice de sección de FRC, no lograremos el resultado deseado, el curso FRC solo da índices encontrados, pero no el alfabeto completo. Además de eso, enfrentaremos un problema con la visualización incorrecta de índices (no estoy seguro de por qué, tal vez algún error en FRC). En el caso del alfabeto ruso, por ejemplo, habrá símbolos totalmente en blanco o “extraños” ($,?, ‘, …).

3) Si tratamos de usar LIC para mostrar índices localizados agradables, enfrentaremos el problema de mapear secciones basadas en datos en FRC para completar “secciones” alfabéticas localizadas en LIC.

4) Después de que decidimos usar LIC y resolver de alguna manera el problema 3) notaremos que LIC colocará la sección “#” en la parte inferior (es decir, el índice de sección más alto) pero FRC colocará los objetos “#” como superiores (es decir, el índice de sección más bajo) – 0). Entonces tendrá un desplazamiento completo de las secciones.

Tomando todo eso en cuenta, decidí “engañar” FRC sin ningún tipo de “hacking” grande, pero hacer que ordene los datos de la forma que necesito (mover todos los objetos que están desde “#” – como la sección al final de la lista).

Aquí está la solución a la que llegué:

Agrego el método de extensión a mi instancia de NSManagedObject para preparar el nombre de clasificación que usaremos en el descriptor de clasificación y en la ruta de la clave de sección para la configuración de FRC. No se necesitan movimientos especiales, excepto aquellos que se describirán a continuación.

El problema 4) se produce debido a los algos de clasificación de FRC (SQL de bajo nivel) que pueden modificarse ligeramente: solo aplicando descriptores de clasificación que son más dependientes de tus datos, predicados y utilizando comparadores predefinidos fijos que no resuelven el problema.

Noté que FRC decide que el símbolo “#” es más bajo que cualquier símbolo del alfabeto opuesto a LIC donde “#” es más alto.

La lógica de FRC es bastante sencilla porque el símbolo “#” en UTF-8 es U + 0023. Y el capital latino “A” es U + 0041, entonces 23 <41. Para hacer que FRC coloque el objeto "#" - like a la sección de índice más alta, necesitamos pasar el símbolo UTF-8 más alto. Para esta fuente http://www.utf8-chartable.de/unicode-utf8-table.pl ese símbolo UTF-8 es U + 1000FF (􀃿). Por supuesto, casi no hay forma de que este símbolo ocurra en la vida real. Usemos U + 100000 para claridad.

El método de actualización del nombre de ordenación se ve así:

 #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"]; } 

Configuración de FRC:

 - (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; } 

Los métodos de delegado FRC son predeterminados. Método de delegado de TV y fuente de datos:

 - (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  sectionInfo in sections) { if([[sectionInfo name] isEqualToString:indexTitle]) { fetchTitleIndex = [sections indexOfObject:sectionInfo]; break; } } return fetchTitleIndex; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { id  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  sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } 

Eso es. Hasta ahora funciona bien. Tal vez funcionará para ti.

¡Encontré una manera fácil de resolver esto!

Simplemente reemplace “#” por “^” en sus datos centrales para que las secciones de su tabla sean “AZ ^”. Mientras que unicode de ‘#’ es más pequeño que ‘A’, ‘^’ ‘s es todo lo contrario. Por lo tanto, no es difícil para usted predecir que ‘^’ seguirá a Z en sus secciones.

Luego, debe reemplazar las secciones del controlador de resultados obtenidos. solo por este par de líneas de código:

 - (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]; }