2009-11-16 11 views
38

J'utilise Core Data pour une vue de table, et je voudrais utiliser la première lettre de chacun de mes résultats comme en-tête de section (afin que je puisse obtenir la section index sur le côté). Y a-t-il un moyen de le faire avec le chemin clé? Quelque chose comme ci-dessous, où j'utilise name.firstLetter comme sectionNameKeyPath (malheureusement, cela ne fonctionne pas).Comment utiliser le premier caractère comme nom de section

Dois-je saisir manuellement la première lettre de chaque résultat et créer mes sections comme ça? Est-il préférable de mettre dans une nouvelle propriété pour simplement tenir la première lettre et l'utiliser comme 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.

Répondre

78

Vous devez juste passer "nom" comme sectionNameKeyPath. Voir ce answer à la question "Données de base soutenu UITableView avec indexation".

MISE À JOUR
Cette solution ne fonctionne que si vous vous souciez que d'avoir la barre de défilement rapide titre d'index. Dans ce cas, vous n'afficherez PAS les en-têtes de section. Voir ci-dessous pour un exemple de code. Sinon, je suis d'accord avec refulgent pour dire qu'une propriété transitoire est la meilleure solution. En outre, lors de la création du NSFetchedResultsController, le sectionNameKeyPath a cette limitation:

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

implémentations boilerplate UITableViewDataSource 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', ajouter un nouvel attribut de chaîne à l'entité concernée dans le modèle et la vérification la case "transitoire".

Il existe plusieurs façons d'implémenter le getter. Si vous créez/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 mets ce droit en haut du dossier de mise en œuvre de mon contrôleur de vue, mais vous pouvez le diviser entre un en-tête approprié et le fichier de mise en œuvre de son 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 

en outre, dans cette version, ne pas oublier de passer 'uppercaseFirstLetterOfName' comme sectionNameKeyPath:

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

Et, décommenter tableView:titleForHeaderInSection: dans la mise en œuvre de UITableViewDataSource:

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

J'ai essayé de mettre le nom comme 'sectionNameKeyPath' mais j'ai fini par avoir une section pour chaque nom retourné. J'ai eu autant de sections qu'il y avait de noms, chaque section avec une seule entrée (le nom). –

+0

Vous n'avez peut-être pas configuré les éléments correctement. Donne la réponse une autre lecture. Cela fonctionne comme annoncé. –

+0

Je ne crois pas que la 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 un chemin clé. – refulgentis

11

Il peut y avoir une manière plus élégante de faire ceci, mais j'ai récemment eu le même problème et ai trouvé cette solution.

D'abord, je défini une propriété transitoire sur les objets que j'appelais firstLetterOfName indexation est-et écrit le getter dans le fichier .m pour 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é ma demande de recherche/entités 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]; 

Side note, à propos de rien: Soyez prudent avec NSFetchedResultsController - c'est pas exactement encore complètement cuit à mon humble avis, et toute situation au-delà des cas simples énumérés dans la documentation, vous serez probablement mieux faire le « vieux façon '.

+0

+1 La propriété transitoire est la meilleure solution lors de l'affichage des en-têtes de section. – gerry3

+1

Attention à 'substringToIndex:'! Si la chaîne est une chaîne UTF-16, cela n'attrape que la première moitié du premier caractère. La bonne façon de le faire est '[aString subStringWithRange: [aString rangeOfComposedCharacterSequenceAtIndex: 1]]' –

+0

Merci pour la réponse. Je ne comprends pas où vous appelez la méthode 'uppercaseFirstLetterOfName'. Devriez-vous appeler cela au lieu de 'caseInsensitiveCompare' ou y a-t-il un autre moyen de l'appeler? –

4

Je résolu cela en utilisant l'UILocalizedIndexCollation comme mentionné dans la NSFetchedResultsController v.s. UILocalizedIndexedCollation question

+1

C'est vraiment la seule façon de le résoudre.UILocalizedIndexCollation a un * lot * de comportement intégré, et le code majusculeFirstLetterOfName suggéré dans d'autres réponses n'est même pas proche. Comme un seul exemple, UILocalizedIndexCollation convertit correctement Katakana japonais demi et pleine largeur dans la section correcte, avec le nom de section désigné par le bon caractère Hiragana - et il fait la bonne chose pour à peu près tous les autres langues dans le monde. – JosephH

1

La façon élégante est de faire faire le « firstLetter » une propriété transitoire, mais dans la pratique qui est lent.

Il est lent à cause d'une propriété transitoire à calculer, l'objet entier doit être en défaut dans la mémoire. Si vous avez beaucoup d'enregistrements, ce sera très, très lent.

La voie rapide, mais inélégant, est de créer une propriété non transitoire « firstLetter » que vous mettez à jour chaque fois que vous définissez votre propriété « name ». Plusieurs façons de le faire: remplacer l'évaluateur "setName:", remplacer "willSave", KVO.

+0

Avez-vous pu le confirmer? Je ne pouvais pas identifier la différence entre les propriétés transitoires et persistantes dans mon projet. –

+0

Je suis en train de questionner la façon "élégante" de commenter la réponse @ gerry3. Et "inélégant" est encore plus "inélégant" qu'il apparaît. Les index doivent être faits à partir de 'UILocalizedIndexedCollation'. Donc, vous devriez réinitialiser chaque entrée sur le changement de suspicion locale pour correspondre à l'assemblage actuel avec votre première lettre. – user500

1

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

0

Voici la solution simple pour obj-c, plusieurs années et une langue en retard. Cela fonctionne et travaille rapidement dans un de mes projets.

D'abord, je créé une catégorie sur NSString, en nommant le fichier NSString + indexation.J'ai écrit une méthode qui retourne la première lettre d'une chaîne

@implementation NSString (Indexing) 

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

Ensuite, j'ai utilisé cette méthode dans la définition du contrôleur de résultat tiré par les cheveux comme suit

_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"name.stringGroupByFirstInitial" 
                       cacheName:nil]; 

Le code ci-dessus en liaison avec les deux stringIndex Cependant, lorsque j'essaie de faire la même chose avec Swift, il lance une exception car il n'aime pas les propriétés transitoires ou le stockage d'un attribut séparé qui ne contient que la première lettre des données de base

avoir un Fonctionne comme un chemin clé (ou une propriété de chaîne calculée - j'ai essayé ça aussi) Si quelqu'un sait comment réaliser la même chose dans Swift, j'aimerais bien savoir comment.

Questions connexes