jeudi 8 mars 2012

Chapitre 4 et le conte de la NSFetchedResultsController

Bon, certaines personnes ont eu des problèmes sporadiques avec le chapitre 4 application comme décrit ici. La solution que je voudrais utiliser, il faudrait être capable de déterminer le nombre de cours, des encarts et supprime la section non engagés qui a une vue de table. Bien que je peux obtenir cette information, je ne peux le faire en accédant à une variable d'instance privée de UITableView. Évidemment, je ne veux pas vous donner tous une solution qui va devenir votre application rejetés au cours du processus d'examen.

Donc, je suis retourné à la planche à dessin. Je n'aime pas cette solution comme beaucoup depuis qu'il nous oblige à reproduire le travail que la vue de table est déjà fait en gardant un décompte ombre d'insertions et des suppressions, mais il semble bien fonctionner et ne pas ajouter trop de complexité. J'ai maintenant un cas de test assez approfondie pour l'insertion et la suppression de lignes dans une table qui utilise une NSFetchedResultsController et cette solution passe, alors croisons les doigts.

La solution


La première étape est d'ajouter une des variables d'instance privées @ NSUInteger à la classe contrôleur qui gère la table et alla chercher le contrôleur résultats. Cela permet de garder un compte courant du nombre de sections insérées et supprimées lors d'une lot de mises à jour de table.

Dans le contexte de l'application de chapitre 4, ce qui signifie en ajoutant la ligne suivante en gras du code HeroListViewController.h:

#import <UIKit/UIKit.h>

#define kSelectedTabDefaultsKey @"Selected Tab"
enum {
kByName = 0,
kBySecretIdentity,
}
;
@class HeroEditController;
@interface HeroListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UITabBarDelegate, UIAlertViewDelegate, NSFetchedResultsControllerDelegate>{

UITableView *tableView;
UITabBar *tabBar;
HeroEditController *detailController;

@private
NSFetchedResultsController *_fetchedResultsController;
NSUInteger sectionInsertCount;
}

@property (nonatomic, retain) IBOutlet UITableView *tableView;
@property (nonatomic, retain) IBOutlet UITabBar *tabBar;
@property (nonatomic, retain) IBOutlet HeroEditController *detailController;
@property (nonatomic, readonly) NSFetchedResultsController *fetchedResultsController;
- (void)addHero;
- (IBAction)toggleEdit;
@end



Maintenant, nous devons passer à la mise en œuvre du fichier, HeroListViewController.m et ajouter une ligne de code pour réinitialiser le compteur d'insérer quand nous serons avisés par le contrôleur chercher des résultats que des changements sont à venir. Pour ce faire, nous ajoutons une ligne de code à l'controllerWillChangeContent méthode:, comme ceci:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
sectionInsertCount = 0;
[self.tableView beginUpdates];
}

Ensuite, nous avons pour incrémenter cette variable lorsque nous insérer une section, et il décrémente chaque fois que nous supprimer une section dans le contrôleur: didChangeSection: atIndex: forChangeType:. Nous faisons cela en ajoutant le code en gras ci-dessous:

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {

case NSFetchedResultsChangeInsert:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1))) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount++;
}


break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) )) {
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount--;
}


break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}

}

Enfin, chaque fois que nous faisons notre vérification de la cohérence dans le contrôleur: didChangeObject: atIndexPath: forChangeType: newIndexPath: nous avons à prendre les inserts en attente et supprime en compte. Puisque nous ne l'cocher plus d'une fois et insérez de nouvelles sections lorsque la vérification échoue, nous avons également incrémenter la variable si nous ne nous insérer de nouvelles lignes. Nous faisons tout cela en ajoutant le code ci-dessous en gras dans cette méthode:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate: {
NSString *sectionKeyPath = [controller sectionNameKeyPath];
if (sectionKeyPath == nil)
break;
NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];
NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];
id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];
for (int i = 0; i < [keyParts count] - 1; i++) {
NSString *onePart = [keyParts objectAtIndex:i];
changedObject = [changedObject valueForKey:onePart];
}

sectionKeyPath = [keyParts lastObject];
NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath] isEqual:currentKeyValue])
break;

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (tableSectionCount + sectionInsertCount != frcSectionCount) {
// Need to insert a section
NSArray *sections = controller.sections;
NSInteger newSectionLocation = -1;
for (id oneSection in sections) {
NSString *sectionName = [oneSection name];
if ([currentKeyValue isEqual:sectionName]) {
newSectionLocation = [sections indexOfObject:oneSection];
break;
}

}

if (newSectionLocation == -1)
return; // uh oh

if (!((newSectionLocation == 0) && (tableSectionCount == 1) && ([self.tableView numberOfRowsInSection:0] == 0))) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:newSectionLocation] withRowAnimation:UITableViewRowAnimationFade];
sectionInsertCount++;
}


NSUInteger indices[2] = {newSectionLocation, 0};
newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices length:2] autorelease];
}

}

case NSFetchedResultsChangeMove:
if (newIndexPath != nil) {

NSUInteger tableSectionCount = [self.tableView numberOfSections];
NSUInteger frcSectionCount = [[controller sections] count];
if (frcSectionCount != tableSectionCount + sectionInsertCount) {
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
sectionInsertCount++;
}



[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath]
withRowAnimation: UITableViewRowAnimationRight
]
;

}

else {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationFade];
}

break;
default:
break;
}

}


Je vais pousser ce nouveau code dans l'archive du projet dès que possible et obtenir qu'il affichés apress.com et iphonedevbook.com, mais voici la version actualisée du projet de chapitre 4 Xcode dans l'intervalle.

Ne vous inquiétez pas si vous ne comprenez pas tout ce qui se passe dans ce code. Ceci est du code conçu pour être méchant complètement générique de sorte que vous n'avez pas à vous soucier du tout. Espérons que ce sera la fin de nos ennuis avec NSFetchedResultsController.

Aucun commentaire: