web-dev-qa-db-fra.com

Comment utiliser le premier caractère comme nom de section

J'utilise des données de base pour une vue sous forme de tableau et j'aimerais utiliser la première lettre de chacun de mes résultats comme en-tête de section (pour que je puisse obtenir l'index de section sur le côté). Y a-t-il un moyen de faire cela avec le chemin clé? Quelque chose comme ci-dessous, où j'utilise name.firstLetter comme sectionNameKeyPath(malheureusement, cela ne fonctionne pas).

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 une nouvelle propriété pour contenir uniquement la première lettre et l'utiliser comme sectionNameKeyPathname__?

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 en japonais, triés par Katakana. Je veux utiliser ces Katakana comme index de section.

38
nevan king

Vous devriez simplement passer "name" comme sectionNameKeyPath. Voir ceci réponse à la question "UITableView avec indexation".

UPDATE
Cette solution ne fonctionne que si vous ne vous souciez que de disposer du scroller de titre rapide. 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 pour dire qu’une propriété transitoire est la meilleure solution. De plus, lors de la création de NSFetchedResultsController, le sectionNameKeyPath a cette limitation:

Si ce chemin d'accès à la clé n'est pas identique à À celui spécifié par le premier descripteur de tri Dans fetchRequest, ils doivent Générer les mêmes ordres relatifs. Par exemple, le premier descripteur de tri dans fetchRequest peut spécifier la clé pour une propriété persistante; sectionNameKeyPath peut spécifier une clé pour une propriété transitoire dérivée de la propriété persistante.

Implémentations UITableViewDataSource de Boilerplate 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];
//}

UPDATE 2  

Pour la nouvelle propriété transitoire 'uppercaseFirstLetterOfName', ajoutez un nouvel attribut de chaîne à l'entité applicable dans le modèle et cochez la case "transitoire".

Il y a plusieurs façons de mettre en œuvre 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 la place tout en haut du fichier d'implémentation de mon contrôleur de vue, mais vous pouvez la scinder entre un en-tête approprié et un fichier d'implémentation distinct):

@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' en tant que sectionNameKeyPath: 

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

Et pour décommenter tableView:titleForHeaderInSection: dans l'implémentation UITableViewDataSource: 

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

Il y a peut-être une manière plus élégante de faire cela, mais j'ai récemment eu le même problème et suis venu avec cette solution.

Tout d'abord, j'ai défini une propriété transitoire sur les objets indexés, appelée firstLetterOfName, et 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, je configure ma requête d’extraction/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];

Remarque secondaire, à propos de rien: soyez prudent avec NSFetchedResultsController - il n’est pas encore complètement cuit à la masse, à mon humble avis, et dans toute situation au-delà des cas simples énumérés dans la documentation, vous ferez probablement mieux de le faire à l’ancienne.

11
refulgentis

J'ai résolu ce problème en utilisant UILocalizedIndexCollation comme indiqué dans le fichier NSFetchedResultsController v.s. UILocalizedIndexedCollation question

4
Brent Priddy

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

1
Scott Gardner

La manière élégante est de faire de la "première lettre" une propriété transitoire, mais en pratique c'est lent.

Il est lent car, pour qu'une propriété transitoire soit calculée, l'objet entier doit être mis en défaut dans la mémoire. Si vous avez beaucoup de disques, ce sera très, très lent.

La manière 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: remplacez le "setName:" assessor, remplacez "willSave", KVO.

1
Corey Floyd

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.

J'ai d'abord créé une catégorie sur NSString, nommant le fichier NSString + Indexing. 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];
}

Puis j'ai utilisé cette méthode dans la définition du contrôleur de résultat récupéré comme suit

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

Le code ci-dessus en conjonction avec les deux méthodes stringIndex fonctionnera sans qu'il soit nécessaire de jouer avec les propriétés transitoires ni de stocker un attribut séparé contenant uniquement la première lettre dans les données de base.

Cependant, lorsque j'essaie de faire la même chose avec Swift, une exception est émise, car elle n'aime pas avoir une fonction faisant partie du chemin de clé (ou une propriété de chaîne calculée - j'ai aussi essayé) Si quelqu'un sait comment faire la même chose à Swift, j'aimerais bien savoir comment.

0
SimonTheDiver