39 votes

Comment utiliser le premier caractère comme nom de section ?

J'utilise Core Data pour une vue en tableau, et j'aimerais utiliser la première lettre de chacun de mes résultats comme en-tête de section (afin de pouvoir obtenir l'index de section sur le côté). Existe-t-il un moyen de faire cela avec le chemin clé ? Quelque chose comme ci-dessous, où j'utilise name.firstLetter comme le sectionNameKeyPath (malheureusement, cela ne fonctionne pas).

Dois-je saisir manuellement la première lettre de chaque résultat et créer mes sections de cette manière ? Est-il préférable d'ajouter une nouvelle propriété qui contiendrait uniquement la première lettre et de l'utiliser comme la propriété sectionNameKeyPath ?

NSFetchedResultsController *aFetchedResultsController = 
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
            managedObjectContext:managedObjectContext
            sectionNameKeyPath:@"name.firstLetter"
            cacheName:@"Root"];

Merci.

**EDIT : ** Je ne sais pas si cela fait une différence, mais mes résultats sont japonais, triés par Katakana. Je veux utiliser ces Katakana comme index de section.

80voto

gerry3 Points 15696

Vous devez simplement passer "name" comme chemin de clé de la sectionNameKeyPath. Voir ceci réponse à la question "Core Data backed UITableView avec indexation".

UPDATE
Cette solution ne fonctionne que si vous ne vous souciez que d'avoir le défilement rapide du titre de l'index. Dans ce cas, vous ne devez PAS afficher les en-têtes de section. Voir ci-dessous pour un exemple de code.

Sinon, je suis d'accord avec refulgentis qu'une propriété transitoire est la meilleure solution. De plus, lors de la création du NSFetchedResultsController, l'attribut sectionNameKeyPath a cette limitation :

Si ce chemin de clé n'est pas le même que celui spécifié par le premier descripteur de tri du descripteur de tri dans fetchRequest, ils doivent générer les mêmes ordres relatifs. Par exemple, le premier descripteur de tri dans fetchRequest pourrait spécifier la clé pour une propriété persistante ; sectionNameKeyPath pourrait spécifier une clé pour une propriété transitoire dérivée de la propriété persistante.

Implémentations de UITableViewDataSource en utilisant NSFetchedResultsController :

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[fetchedResultsController sections] count];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [fetchedResultsController sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

// Don't implement this since each "name" is its own section:
//- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
//    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
//    return [sectionInfo name];
//}

MISE À JOUR 2

Pour la nouvelle propriété transitoire "uppercaseFirstLetterOfName", ajoutez un nouvel attribut string à l'entité concernée dans le modèle et cochez la case "transient".

Il y a plusieurs façons d'implémenter le getter. Si vous générez/créez des sous-classes, vous pouvez l'ajouter dans le fichier d'implémentation (.m) de la sous-classe.

Sinon, vous pouvez créer une catégorie sur NSManagedObject (je l'ai placé en haut du fichier d'implémentation de mon contrôleur de vue, mais vous pouvez le répartir entre un en-tête approprié et un fichier d'implémentation propre) :

@interface NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName;
@end

@implementation NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName {
    [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
    NSString *aString = [[self valueForKey:@"name"] uppercaseString];

    // support UTF-16:
    NSString *stringToReturn = [aString substringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:0]];

    // OR no UTF-16 support:
    //NSString *stringToReturn = [aString substringToIndex:1];

    [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
    return stringToReturn;
}
@end

De plus, dans cette version, n'oubliez pas de passer 'uppercaseFirstLetterOfName' comme sectionNameKeyPath :

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext
sectionNameKeyPath:@"uppercaseFirstLetterOfName" // this key defines the sections
cacheName:@"Root"];

Et, pour décomposer tableView:titleForHeaderInSection: dans l'implémentation de UITableViewDataSource :

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo name];
}

0 votes

J'ai essayé de mettre le nom en tant que sectionNameKeyPath mais je me suis retrouvé avec une section pour chaque nom retourné. J'ai obtenu autant de sections qu'il y avait de noms, chaque section ne comportant qu'une seule entrée (le nom).

0 votes

Vous n'avez peut-être pas tout mis en place correctement. Relisez la réponse. Elle fonctionne comme prévu.

0 votes

Je ne crois pas que cette réponse "fonctionne" en soi, je ne sais pas pourquoi le répondant a agi comme si c'était un miracle d'utiliser le prénom comme chemin clé.

12voto

refulgentis Points 1862

Il existe peut-être un moyen plus élégant de procéder, mais j'ai récemment rencontré le même problème et j'ai trouvé cette solution.

Tout d'abord, j'ai défini une propriété transitoire sur les objets que j'indexais, appelée firstLetterOfName, et j'ai écrit le getter dans le fichier .m de l'objet. e.x.

- (NSString *)uppercaseFirstLetterOfName {
    [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
    NSString *stringToReturn = [[self.name uppercaseString] substringToIndex:1];
    [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
    return stringToReturn;
}

Ensuite, j'ai configuré mes requêtes/entités de récupération pour utiliser cette propriété.

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Object" inManagedObjectContext:dataContext];
[request setEntity:entity];
[NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];

Remarque complémentaire, à propos de rien : faites attention avec NSFetchedResultsController - il n'est pas encore tout à fait au point, à mon avis, et pour toute situation dépassant les cas simples énumérés dans la documentation, vous ferez probablement mieux de procéder à l'ancienne.

0 votes

+1 La propriété Transient est la meilleure solution pour afficher les en-têtes de section.

1 votes

Méfiez-vous substringToIndex: ! Si la chaîne est une chaîne UTF-16, cela ne saisira que la première moitié du premier caractère. La façon correcte de procéder est [aString subStringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:1]]

0 votes

Merci pour la réponse. Je ne comprends pas où vous appelez le uppercaseFirstLetterOfName méthode. Vous devriez appeler cette méthode au lieu de caseInsensitiveCompare ou y a-t-il une autre façon de l'appeler ?

4voto

Brent Priddy Points 2889

J'ai résolu ce problème en utilisant la collation UILocalizedIndexCollation, comme indiqué dans l'aide-mémoire. NSFetchedResultsController v.s. UILocalizedIndexedCollation question

1 votes

C'est vraiment la seule façon de le résoudre. UILocalizedIndexCollation possède une fonction lot de comportement intégré, et le code uppercaseFirstLetterOfName suggéré dans d'autres réponses ne s'en approche même pas. Pour ne citer qu'un seul exemple, UILocalizedIndexCollation regroupe correctement les katakana japonais en demi-largeur et en pleine largeur dans la section correcte, le nom de la section étant indiqué par le caractère Hiragana correct - et il fait la bonne chose pour pratiquement toutes les autres langues du monde également.

2voto

Corey Floyd Points 16747

La méthode la plus élégante consiste à faire de la "première lettre" une propriété transitoire, MAIS en pratique, c'est lent.

Elle est lente parce que pour calculer une propriété transitoire, l'objet entier doit être mis en mémoire. Si vous avez beaucoup d'enregistrements, ce sera très, très lent.

La méthode rapide, mais peu élégante, consiste à créer une propriété "firstLetter" non transitoire que vous mettez à jour chaque fois que vous définissez votre propriété "name". Il existe plusieurs façons de le faire : remplacer l'assesseur "setName :", remplacer "willSave", KVO.

0 votes

Avez-vous pu le confirmer ? Je n'ai pas pu repérer la différence entre les propriétés transitoires et persistantes dans mon projet.

0 votes

Je m'interroge sur la manière "élégante" dans le commentaire sur la réponse de @gerry3. Et "inélégante" est encore plus "inélégante" qu'il n'y paraît. Les index devraient être faits à partir de UILocalizedIndexedCollation . Vous devez donc réinitialiser chaque entrée de la suspicion de changement de locale pour faire correspondre la collation actuelle avec votre première lettre.

1voto

Scott Gardner Points 692

Voir ma réponse à une question similaire ici Dans cet article, je décris comment créer des index sectionKey localisés qui sont persistants (parce que vous ne pouvez pas trier sur des attributs transitoires dans un NSFetchedResultsController).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X