NSFetchedResultsController con secciones creadas por la primera letra de una cadena

Aprendizaje de datos básicos en el iPhone. Parece que hay pocos ejemplos en Core Data poblando una vista de tabla con secciones. El ejemplo de CoreDataBooks utiliza secciones, pero se generan a partir de cadenas completas dentro del modelo. Quiero organizar la tabla Core Data en secciones por la primera letra de un apellido, al estilo de la Libreta de direcciones.

Podría entrar y crear otro atributo, es decir, una sola letra, para cada persona con el fin de actuar como la división de sección, pero esto parece kludgy.

Esto es lo que estoy empezando … el truco parece estar engañando a la sectionNameKeyPath :

 - (NSFetchedResultsController *)fetchedResultsController { //.........SOME STUFF DELETED // Edit the sort key as appropriate. NSSortDescriptor *orderDescriptor = [[NSSortDescriptor alloc] initWithKey:@"personName" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:orderDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"personName" cacheName:@"Root"]; //.... } 

El enfoque de Dave DeLong es bueno, al menos en mi caso, siempre que omitas un par de cosas. Así es como me funciona:

  • Agregue un nuevo atributo de cadena opcional a la entidad llamada “lastNameInitial” (o algo por el estilo).

    Haga que esta propiedad sea transitoria. Esto significa que Core Data no se molestará en guardarlo en su archivo de datos. Esta propiedad solo existirá en la memoria, cuando la necesite.

    Genera los archivos de clase para esta entidad.

    No te preocupes por un colocador de esta propiedad. Crea este getter (esto es la mitad de la magia, en mi humilde opinión)


 // THIS ATTRIBUTE GETTER GOES IN YOUR OBJECT MODEL - (NSString *) committeeNameInitial { [self willAccessValueForKey:@"committeeNameInitial"]; NSString * initial = [[self committeeName] substringToIndex:1]; [self didAccessValueForKey:@"committeeNameInitial"]; return initial; } // THIS GOES IN YOUR fetchedResultsController: METHOD // Edit the sort key as appropriate. NSSortDescriptor *nameInitialSortOrder = [[NSSortDescriptor alloc] initWithKey:@"committeeName" ascending:YES]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]]; NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"committeeNameInitial" cacheName:@"Root"]; 

ANTERIORMENTE: Siguiendo los pasos iniciales de Dave hacia la carta, se generaron problemas donde falle en setPropertiesToFetch con una excepción de argumento no válido. He registrado el código y la información de depuración a continuación:

 NSDictionary * entityProperties = [entity propertiesByName]; NSPropertyDescription * nameInitialProperty = [entityProperties objectForKey:@"committeeNameInitial"]; NSArray * tempPropertyArray = [NSArray arrayWithObject:nameInitialProperty]; // NSARRAY * tempPropertyArray RETURNS: // {type = immutable, count = 1, values = ( // 0 : (), // name committeeNameInitial, isOptional 1, isTransient 1, // entity CommitteeObj, renamingIdentifier committeeNameInitial, // validation predicates (), warnings (), versionHashModifier (null), // attributeType 700 , attributeValueClassName NSString, defaultValue (null) // )} // NSInvalidArgumentException AT THIS LINE vvvv [fetchRequest setPropertiesToFetch:tempPropertyArray]; // *** Terminating app due to uncaught exception 'NSInvalidArgumentException', // reason: 'Invalid property (), // name committeeNameInitial, isOptional 1, isTransient 1, entity CommitteeObj, // renamingIdentifier committeeNameInitial, // validation predicates (), warnings (), // versionHashModifier (null), // attributeType 700 , attributeValueClassName NSString, // defaultValue (null) passed to setPropertiesToFetch: (property is transient)' [fetchRequest setReturnsDistinctResults:YES]; NSSortDescriptor * nameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey:@"committeeNameInitial" ascending:YES] autorelease]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]]; NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"committeeNameInitial" cacheName:@"Root"]; 

Creo que tengo otra opción, esta usa una categoría en NSString …

 @implementation NSString (FetchedGroupByString) - (NSString *)stringGroupByFirstInitial { if (!self.length || self.length == 1) return self; return [self substringToIndex:1]; } @end 

Ahora un poco más adelante, mientras construyes tu FRC:

 - (NSFetchedResultsController *)newFRC { NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:awesomeRequest managedObjectContext:coolManagedObjectContext sectionNameKeyPath:@"lastName.stringGroupByFirstInitial" cacheName:@"CoolCat"]; return frc; } 

Este es ahora mi enfoque favorito. Mucho más limpio / más fácil de implementar. Además, no es necesario realizar ningún cambio en la clase de modelo de objeto para admitirlo. Esto significa que funcionará en cualquier modelo de objeto, siempre que el nombre de la sección apunte a una propiedad basada en NSString

Así es cómo puede hacer que funcione:

  • Agregue un nuevo atributo de cadena opcional a la entidad llamada “lastNameInitial” (o algo por el estilo).
  • Haga que esta propiedad sea transitoria. Esto significa que Core Data no se molestará en guardarlo en su archivo de datos. Esta propiedad solo existirá en la memoria, cuando la necesite.
  • Genera los archivos de clase para esta entidad.
  • No te preocupes por un colocador de esta propiedad. Crea este getter (esto es la mitad de la magia, en mi humilde opinión)

      - (NSString *) lastNameInitial { 
    [self willAccessValueForKey: @ "lastNameInitial"];
    NSString * initial = [[self lastName] substringToIndex: 1];
    [self didAccessValueForKey: @ "lastNameInitial"];
    retorno inicial;
    }
  • En su solicitud de búsqueda, solicite SOLO esta Descripción de propiedad, como tal (esta es otra cuarta parte de la magia):

      NSDictionary * entityProperties = [myEntityDescription propertiesByName]; 
    NSPropertyDescription * lastNameInitialProperty = [entityProperties objectForKey: @ "lastNameInitial"];
    [fetchRequest setPropertiesToFetch: [NSArray arrayWithObject: lastNameInitialProperty]];
  • Asegúrate de que tu solicitud de recuperación SOLO arroje resultados distintos (este es el último cuarto de la magia):

      [fetchRequest setReturnsDistinctResults: YES]; 
  • Ordena tus resultados por esta carta:

      NSSortDescriptor * lastNameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey: @ "lastNameInitial" ascendente: YES] liberación automática]; 
    [fetchRequest setSortDescriptors: [NSArray arrayWithObject: lastNameInitialSortOrder]];
  • ejecuta la solicitud, y mira lo que te da.

Si entiendo cómo funciona esto, entonces supongo que devolverá una matriz de NSManagedObjects, cada uno de los cuales solo tiene la propiedad lastNameInitial cargada en la memoria, y que son un conjunto de iniciales de distintos nombres.

Buena suerte e informar sobre cómo funciona esto. Acabo de hacer esto de la cabeza y quiero saber si esto funciona. =)

Me gusta la respuesta de Greg Combs arriba. Hice una pequeña modificación para que cadenas como “Smith” y “smith” aparezcan en la misma sección convirtiendo las cadenas en mayúsculas:

 - (NSString *)stringGroupByFirstInitial { NSString *temp = [self uppercaseString]; if (!temp.length || temp.length == 1) return self; return [temp substringToIndex:1]; } 

Me encuentro con este problema todo el tiempo. La solución que parece mejor a la que siempre vuelvo es simplemente darle a la entidad una primera propiedad real inicial. Ser un campo real proporciona una búsqueda y ordenamiento más eficientes, ya que puede configurar el campo para indexar. No parece que sea demasiado trabajo extraer la primera inicial y llenar un segundo campo con ella cuando los datos se importan / crean por primera vez. Tienes que escribir ese código de análisis inicial de cualquier manera, pero podrías hacerlo una vez por entidad y nunca más. Los inconvenientes parecen ser que estás almacenando un personaje extra por entidad (y la indexación) realmente, eso es insignificante.

Una nota extra Me niego a modificar el código de entidad generado. Tal vez me falta algo, pero las herramientas para generar entidades de CoreData no respetan ningún código que pueda haber puesto ahí. Cualquiera de las opciones que selecciono al generar el código elimina cualquier personalización que haya realizado. Si llené mis entidades con pequeñas funciones inteligentes, entonces necesito agregar un montón de propiedades a esa entidad, no puedo regenerarlas fácilmente.

veloz 3

primero, crea la extensión a NSString (porque CoreData usa básicamente NSString)

 extension NSString{ func firstChar() -> String{ if self.length == 0{ return "" } return self.substring(to: 1) } } 

Luego ordena usando keypath FirstChar, en mi caso, lastname.firstChar

 request.sortDescriptors = [ NSSortDescriptor(key: "lastname.firstChar", ascending: true), NSSortDescriptor(key: "lastname", ascending: true), NSSortDescriptor(key: "firstname", ascending: true) ] 

Y finalmente use la primera ruta de acceso de Key de Harán para sectionNameKeyPath

 let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name") 

Creo que tengo una mejor manera de hacer esto. En lugar de usar propiedad transitoria, aparecerá a la vista. Vuelva a calcular la propiedad derivada de NSManagedObject y guarde el contexto. Después de los cambios, puede volver a cargar la vista de tabla.

Aquí hay un ejemplo de cómo calcular el número de bordes de cada vértice, luego ordena los vértices por el número de bordes. En este ejemplo, Capsid es el vértice, el tacto es el borde.

 - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.tableView endUpdates]; [self.tableView reloadData]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Capsid"]; NSError *error = nil; NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error]; if (error) { NSLog(@"refresh error"); abort(); } for (Capsid *capsid in results) { unsigned long long sum = 0; for (Touch *touch in capsid.vs) { sum += touch.count.unsignedLongLongValue; } for (Touch *touch in capsid.us) { sum += touch.count.unsignedLongLongValue; } capsid.sum = [NSNumber numberWithUnsignedLongLong:sum]; } if (![self.managedObjectContext save:&error]) { NSLog(@"save error"); abort(); } } - (NSFetchedResultsController *)fetchedResultsController { if (__fetchedResultsController != nil) { return __fetchedResultsController; } // Set up the fetched results controller. // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@"Capsid" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; // Set the batch size to a suitable number. [fetchRequest setFetchBatchSize:20]; // Edit the sort key as appropriate. // NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO]; // NSSortDescriptor *sumSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sum" ascending:NO]; // NSArray *sortDescriptors = [NSArray arrayWithObjects:sumSortDescriptor, nil]; [fetchRequest setReturnsDistinctResults:YES]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sum" ascending:NO]; NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; NSError *error = nil; if (![self.fetchedResultsController performFetch:&error]) { /* Replace this implementation with code to handle the error appropriately. abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. */ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return __fetchedResultsController; }