NSFetchedResultsController vs UILocalizedIndexedCollation

Estoy intentando usar un FRC con datos de idiomas mixtos y quiero tener un índice de sección.

Parece que de la documentation debe poder anular el FRC

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

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

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

Usando las siguientes properties

 @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 utilizar el protocolo FRCDelegate para recibir notifications de las actualizaciones. Parece que no hay una buena forma de hacer que estos dos objects funcionen bien juntos.

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

  1. Cree un atributo de cadena llamado "sectionKey" para cada atributo de orderación dentro de cada entidad en su model de datos principales. El atributo sectionKey será un valor calculado derivado de un atributo base (por ejemplo, un atributo de nombre o título). Debe persistirse porque (actualmente) no se puede usar una propiedad transitoria en un descriptor de sorting para una request de recuperación. Habilite la indexing en cada secciónKey y el atributo base para qué sorting se ofrecerá. Para aplicar esta actualización a una aplicación existente, necesitará realizar una migration ligera y también include una rutina para actualizar las bases de datos preexistentes.

  2. Si está sembrando datos (p. Ej., Para poblar nuevas instalaciones con un set de datos estándar o para crear bases de datos SQLite localizadas para cada idioma de destino, de las cuales se copyrá una en el lanzamiento inicial), en ese código, calcule y actualice cada uno sección de la entidadAtributo (s) key (s). Las opiniones varían en cuanto al enfoque "mejor" para sembrar datos, sin embargo, vale la pena señalar que un puñado de files de plist para cada idioma (que normalmente van desde unos pocos bytes hasta 20k, incluso para una list compuesta por cientos de valores) se irán una huella global mucho más pequeña que una database SQLite individual para cada idioma (que comienza en aproximadamente 20k cada uno). Como nota al margen, Microsoft Excel para Mac puede configurarse para proporcionar sorting localizada de lists habilitando las características de idioma (3).

  3. En el constructor del controller de resultados obtenidos, clasifique en el atributo sectionKey y base y pase la secciónKey para la ruta de la key del nombre de la sección.

  4. Agregue la lógica de cálculo para actualizar el (los) atributo (s) de la (s) key (s) en todas las inputs de usuario de adición o edición, por ejemplo, en textFieldDidEndEditing :.

¡Eso es! No se particionó manualmente los objects capturados en una matriz de arreglos. NSFetchedResultsController hará la intercalación localizada por usted. Por ejemplo, en el caso de chino (simplificado), los objects buscados serán indexados por pronunciación fonética (4).

(1) De Apple IOS Developer Library> Temas de progtwigción de internationalization > Internacionalización y localización . (2) 3_SimpleIndexedTableView de TableViewSuite . (3) Cómo habilitar las funciones de idioma chino en Microsoft Office para Mac . (4) El idioma chino se clasifica comúnmente por count de golpes o pronunciación fonética.

Brent, mi solución se basa en FRC y obtengo secciones de la búsqueda especificando un atributo transitorio en mi object model que devuelve el nombre de la sección para el object. Utilizo UIlocalizedIndexedCollation solo en la implementación del atributo getter y confío en la implementación de FRC en el controller de vista de tabla. Por supuesto, uso localizedCaseInsensitiveCompare como selector de orderació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 sorting del DB. Todo lo demás funciona bien.

Al enfrentar el mismo problema recientemente me hace search en la web (stackoverflow en primer lugar) la solución adecuada para que NSFetchedResultsController (FRC) y UILocalizedIndexedCollation (LIC) trabajen juntos. La mayoría de las soluciones de búsqueda no fueron lo suficientemente buenas como para cumplir con todos los requisitos. Es importante mencionar que no podemos usar LIC para orderar los objects buscados en la forma en que necesita un curso, ya que tendremos un performance enorme y FRC no usaría todas las ventajas.

Entonces, aquí está el problema en general:

1) Tenemos DB con algún tipo de datos que queremos search y mostrar utilizando FRC en una list (UITableView) con índices (similar a Contacts.app). Necesitamos pasar key de valor de object para que FRC pueda tomar una decisión de orderación.

2) Incluso si agregamos un campo especial a nuestros models CoreData para la sorting de secciones y usamos los títulos de índice de la sección de FRC, no lograremos el resultado deseado, el curso FRC solo proporciona los índices encontrados, pero no completa el alfabeto. Además de eso, nos enfrentaremos a un problema de visualización de índices incorrectos (no estoy realmente seguro de por qué es así, 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 vamos a tratar de usar LIC para mostrar buenos índices localizados, nos enfrentaremos al problema de mapear secciones basadas en datos en FRC para completar "secciones" alfabéticas localizadas en LIC.

4) Después de que decidimos utilizar LIC y de alguna manera resolver el problema 3) notaremos que LIC colocará la sección "#" en la parte inferior (es decir, el índice más alto de la sección) pero FRC colocará "#" como objects en la parte superior (es decir, índice de sección más bajo – 0). Por lo tanto, tendrá secciones completas de desplazamiento.

Tomando todo eso en un recuento, decidí "engañar" a FRC sin una gran "piratería", pero hago que ordere los datos de la manera que necesito (mueva todos los objects que van desde "#", como la sección al final de la list).

Esta es la solución a la que vine:

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

Problema 4) se produce debido a la sorting de FRC algos (SQL de bajo nivel) que puede modificarse ligeramente: solo mediante la aplicación de descriptores de sorting que son más dependientes de su información, pnetworkingicados y el uso de comparadores pnetworkingefinidos fijos que no resuelven el problema.

Noté que FRC decide que el símbolo "#" es más bajo que cualquier símbolo alfabético opuesto al 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, por lo que 23 <41. Para hacer que FRC coloque el object "#" como object en la sección de índice más alto, debemos pasar el símbolo UTF-8 más alto. Para esta fuente http://www.utf8-chartable.de/unicode-utf8-table.pl , el símbolo UTF-8 es U + 1000FF (􀃿). Por supuesto, casi no hay forma de que este símbolo ocurra en la vida real. Permite usar U + 100000 para la claridad.

El método de actualización de nombre de orderación parece algo como esto:

 #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 methods de delegado de FRC son pnetworkingeterminados. Delegado de TV y methods de origen 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 <NSFetchedResultsSectionInfo> sectionInfo in sections) { if([[sectionInfo name] isEqualToString:indexTitle]) { fetchTitleIndex = [sections indexOfObject:sectionInfo]; break; } } return fetchTitleIndex; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { id <NSFetchedResultsSectionInfo> 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 <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } 

Eso es. Hasta ahora funciona bien. Quizás funcione para usted.

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

Simplemente reemplace "#" por "^" en sus datos centrales para que las secciones de su vista de tabla sean "AZ ^". Mientras unicode de '#' es más pequeño que 'A', '^' 's es justo lo contrario. Por lo tanto, no es difícil para usted pnetworkingecir que '^' seguirá a Z en sus secciones.

Luego, debe replace las secciones del controller de resultados buscados. 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]; }