14

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

Parece que a partir de la documentación que debe ser capaz de anular el

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

y de FRC continuación, utilizar el UILocalizedIndexedCollation tener un índice y secciones localizadas. 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 clasificación manual mencionado en el ejemplo ejemplo UITableView + UILocalizedIndexedCollation (código de ejemplo incluido en tengo este trabajo)

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 utilizar 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.

Respuesta

3

Brent, mi solución se basa 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 BD. Todo lo demás funciona bien

+1

Tendré que probar esto la próxima vez que se me presente este problema (la solución existente funciona bien). Solo una nota: nunca se debe usar localizedCaseInsensitiveCompare porque algunos idiomas (como el francés, creo) tienen en cuenta el caso al ordenar; use localizedStandardCompare. Esto fue mencionado en el último video localizador WWDC –

+1

De todas las soluciones que he encontrado para ordenar y mostrar datos indexados de un FRC, esta es la más elegante. ¿Has encontrado una forma de mover la sección # hasta el final? Siento que una de las opciones de clasificación de cadenas haría esto y es extraño que sectionIndexTitles de UILocalizedIndexedCollation se ordene de manera diferente al orden que los comparadores de cadenas clasifican. –

+0

Esto no es una bala de plata, así que ten cuidado. Como se observa en otra respuesta, no se puede construir una 'NSFetchRequest' con una clasificación en una propiedad transitoria como la que ha descrito. (Al menos, no si está usando un almacén de datos Core respaldado por SQLite). Por lo tanto, si su orden de clasificación es diferente del orden de la intercalación, el 'NSFetchedResultsController' generará un error y no devolverá ninguna fila. Por ejemplo, si el apellido de un usuario comienza con una letra minúscula, se ordenará * después de * todos los demás nombres, pero la intercalación tratará de ponerlo con otros nombres que comiencen con la misma letra (pero en mayúscula). – jsadler

6

Puesto que no se puede clasificar en una propiedad transitoria, la solución que he implementado es ...

  1. crear un atributo de cadena llamado "sectionKey" para cada atributo clasificable dentro de cada entidad en el modelo de datos básicos. 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á generando 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 inicial), en ese código , calcule y actualice los atributos clave de la sección de cada 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 van desde unos pocos bytes hasta 20k, incluso para una lista compuesta por varios cientos de valores) saldrán 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, ordene los atributos sectionKey y base, y pase 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 agregadas o editadas, por ejemplo, en textFieldDidEndEditing :.

Eso es todo! 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 programación de internacionalización>Internationalization and Localization. (2) 3_SimpleIndexedTableView of the TableViewSuite. (3) How to enable Chinese language features in Microsoft Office for Mac. (4) El idioma chino se suele ordenar por recuento de trazos o pronunciación fonética.

+1

¿Puede explicar el "cálculo" que está haciendo para generar el valor de la sección clave? –

2

Hacer frente al mismo problema recientemente me hace buscar en la web (stackoverflow en primer lugar) para 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.

Por lo tanto, aquí está el problema en general:

1) Tenemos DB con algún tipo de datos que queremos traer y mostrar el uso de la CRF 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) Aunque agreguemos un campo especial a nuestros modelos CoreData para secciones ordenando y utilizando 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, 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 usar LIC y resolver el problema 3) notaremos que LIC colocará la sección "#" abajo (es decir, el índice de sección más alto) pero FRC colocará "#" - como objetos en la parte superior (es decir, índice de sección más baja - 0). Entonces tendrá un desplazamiento completo de las secciones.

Teniendo todo esto en cuenta, decidí "engañar" a FRC sin grandes "hackeos" pero hacer que ordenara los datos de la manera que necesito (mover todos los objetos que están de "#" - como la sección al final de la lista)

Aquí está la solución que he venido para:

agrego método de extensión a mi ejemplo NSManagedObject para preparar ordenar el nombre que vamos a utilizar en el descriptor tipo y sección de ruta de la clave para la configuración de la CRF. No se necesitan movimientos especiales, excepto aquellos que se describirán a continuación.

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 ordenamiento que son más dependientes de sus 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 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 que esta fuente http://www.utf8-chartable.de/unicode-utf8-table.pl ese símbolo UTF-8 sea U + 1000FF(). Por supuesto, casi no hay forma de que este símbolo ocurra en la vida real. Usemos U + 100000 para claridad.

Tipo de orden nombre de la actualización se ve 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"]; 
} 

FRC configuración:

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

métodos FRC delegados son por defecto. Método de fuente de datos y delegado de TV:

- (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 todo. Hasta ahora funciona bien. Tal vez funcionará para ti.

+1

altougt esto corrige los títulos de las secciones y el índice. Los contenidos de la sección siguen siendo incorrectos. Por ejemplo, los caracteres chinos tendrán el título correcto de la sección en inglés, pero están en una sección incorrecta. Terminaré con 2 secciones con el mismo nombre: S –

+0

@ JoãoNunes ¿Has descubierto cómo lidiar con los personajes chinos? – user754905

+0

@ user754905 no. Pero tampoco miraba esto más. –

2

¡He encontrado una manera fácil de resolver esto!

Simplemente reemplace "#" por "^" en sus datos principales para que las secciones de su tabla sean "A-Z ^". 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 con este par de líneas 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]; 
} 
Cuestiones relacionadas