25

Ce que je veux faire est assez simple. Dans mon UITableViewController, je veux charger des données à partir de plusieurs NSFetchedResultControllers (j'ai plusieurs entités dans mon modèle de données) et mettre les données de chacun dans une section différente dans la vue tabulaire. Ainsi, par exemple, tous les éléments extraits du premier NSFetchedResultController iront dans la section 0 de l'UITableView, les éléments récupérés de l'autre dans la section 1, etc.Données de base: UITableView avec plusieurs NSFetchedResultControllers

Le projet de modèle de base de données ne montre pas comment fais ceci. Tout (principalement les chemins d'index) est codé sans prendre en compte les sections (il n'y a pas de section dans le template par défaut) et tout est pris depuis un seul NSFetchedResultController. Y a-t-il des exemples de projets ou de documentation qui démontrent cela?

Merci

Répondre

24

Supposons un instant qui suit dans votre tête (code ci-dessous sera légèrement bâclée, mes excuses):

NSFetchedResultsController *fetchedResultsController1; // first section data 
NSFetchedResultsController *fetchedResultsController2; // second section data 

Laissez la table sais que vous voulez avoir 2 sections:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 
    return 2; // you wanted 2 sections 
} 

Donnez-lui les titres de section:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { 
    return [NSArray arrayWithObjects:@"First section title", @"Second section title", nil]; 
} 

Laissez la table savoir combien de lignes il y a par sections:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    if (section == 0) { 
     return [[fetchedResultsController1 fetchedObjects] count]; 
    } else if (section == 1) { 
     return [[fetchedResultsController2 fetchedObjects] count]; 
    } 

    return 0; 
} 

Construire la cellule:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    ... // table cell dequeue or creation, boilerplate stuff 

    // customize the cell 
    if (indexPath.section == 0) { 
     // get the managed object from fetchedResultsController1 
     // customize the cell based on the data 
    } else if (indexPath.section == 1) { 
     // get the managed object from fetchedResultsController2 
     // customize the cell based on the data 
    } 

    return cell; 
} 
+0

Merci pour la réponse détaillée. Cette partie semble assez simple, mais il existe deux autres méthodes dont je ne suis pas sûr (ce qu'elles font et si elles ont besoin de changements): http://pastebin.ca/1805761 – indragie

+0

Cela dépend de ce que votre l'application fait; il m'est difficile de répondre sans en savoir beaucoup plus sur l'application, le design, etc. Cependant, vous avez probablement un choix sûr à faire en envoyant à la table un message reloadData. – Giao

+0

Géré pour obtenir ce travail avec un peu de bricolage :-) Merci – indragie

3

L'extension de la solution de Giao avec deux NSFetchedResultsControllers - nous devons nous rappeler notre NSFetchedResultsController ne sais pas nos deux sections et retourné NSIndexPathes sera toujours pour la première section.

Alors, quand nous obtenons un objet dans la configuration cellulaire:

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 
    if (!cell) { 
     [tableView registerNib:[UINib nibWithNibName:@"cell" bundle:nil] forCellReuseIdentifier:@"cell"]; 
     cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 
    } 
    [self configureCell:cell atIndexPath:indexPath]; 
    return cell; 
} 

-(void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath *)indexPath { 
    if (indexPath.section == 0) { 
     NSManagedObject *object = [self.fetchedResults1 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; 
     //use object to configure cell 
    } else { 
     NSManagedObject *object = [self.fetchedResults2 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; 
     //use object to configure cell 
    } 
} 

cellules Mise à jour en NSFetchedResultsController remarqué quelques changements:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 
    NSIndexPath *customIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(controller == self.fetchedResultsController1)?0:1]; 
    NSIndexPath *customNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:(controller == self.fetchedResultsController2)?0:1]; 
    switch(type) { 
     case NSFetchedResultsChangeInsert: 
      [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
     case NSFetchedResultsChangeDelete: 
      [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
     case NSFetchedResultsChangeUpdate: 
      [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 
      break; 
     case NSFetchedResultsChangeMove: 
      [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
    } 
} 
+0

Bonne réponse, mais après la relance de l'App «performFetch» ​​doit être appelé sinon le contexte perd les ManagedObjects récupérés. De l'aide? – Goppinath

1

plusieurs contrôleurs fetch (et éventuellement plusieurs entités) est la mauvaise approche . La bonne solution consiste à utiliser le sectionNameKeyPath param pour NSFetchedResultController pour regrouper les résultats dans plusieurs sections. Si vous pensez différemment à vos entités, elles sont peut-être la même entité et à la place vous pouvez utiliser une propriété itemType que vous pouvez ensuite sectionner (et vous devez également trier dessus). Par exemple. disons que j'avais des entités Hops et Grains alors je pourrais les changer en Ingrédient et avoir une propriété int_16 ingrédientType dont j'ai alors une énumération dans le code pour stocker les valeurs hopType = 0, grainType = 1. Après tout l'ingrédient est juste un nom et un poids, qui partagent tous les deux.

Si toutefois vos entités ont vraiment un ensemble distinct de propriétés, alors la solution correcte est de créer une entité abstraite parent qui a une propriété que vous pouvez utiliser pour la section, par ex. sortOrder, sectionID ou type. Lorsque vous créez ensuite un contrôleur d'extraction et extrayez l'entité parent abstraite, vous obtenez des résultats contenant toutes les sous-entités. Par exemple, dans l'application Notes, ils ont une entité abstraite NoteContainer qui a des sous-entités Account et Folder.De cette façon, ils peuvent utiliser un seul contrôleur de récupération pour afficher le compte dans la première cellule de la section, puis avoir tous les dossiers dans les cellules suivantes. Par exemple. Toutes les notes iCloud (est en fait le compte), puis Notes (est le dossier par défaut), suivi de tous les dossiers personnalisés, puis le dossier de la corbeille. Ils utilisent une propriété sortOrder et le dossier par défaut est 1, les dossiers personnalisés sont tous 2 et la corbeille est 3. Ensuite, en ajoutant cela comme un descripteur de tri, ils peuvent afficher les cellules dans l'ordre souhaité. C'est un peu différent de vos besoins car ils ont les 2 entités mélangées dans différentes sections, mais vous pouvez toujours en faire usage avec des propriétés de tri différentes.

La morale de l'histoire est de ne pas combattre le cadre, l'embrasser :-)

Questions connexes