jeudi 8 mars 2012

SuperDB Core Data App avec les sections

Si vous avez obtenu grâce les premiers chapitres de la Plus l'iPhone 3 de développement, Vous pourriez vous demander pourquoi nous avons inclus un sectionNameKeyPath quand on n'a pas réellement diviser la table en sections. Quel est le point d'avoir récupéré le contrôleur résultats, utiliser des sections si elles ne sont pas affichées?

La vérité de l'affaire, c'est que nous avions initialement prévu de prendre l'application SuperDB plus loin que nous avons pu. Malheureusement, nous avons atteint un point où nous avons dû le couper et passer à d'autres sujets dans l'ordre à la fois à répondre à nos délais et à venir dans à un nombre de pages raisonnable (comme il était, nous sommes venus en 250 pages sur ce que nous contractés pour ). Bon, nous n'avons pas vraiment répondre à nos échéances, mais nous aurions manqué de plus.

Dave et moi avons convenu d'arrêter de travailler sur les données fondamentales pour obtenir le livre fait et laisser de la place pour les autres sujets, mais nous a laissé ouverte la possibilité d'étendre l'application davantage ici dans mon blog. Afin d'être capable de faire cela, nous avons laissé dans quelques vestiges du plan original pour le rendre plus facile d'étendre l'application ici.

Voici la première extension, qui est d'ajouter des sections à la table alphabétique, comme ceci:

Screen shot 2009-12-29 at 5.14.44 PM.png


Continuons à partir du code dans le 07 - SuperDB du projet. Vous pouvez télécharger la version révisée du here. Faire une nouvelle copie de celui-ci si vous le souhaitez. La première chose que nous devons faire est d'ajouter une méthode déléguée tableview qui renvoie le titre qui sera affiché dans chaque section. Pour ce faire, ajoutez le code suivant à la HeroListViewController.m, Près d'autres méthodes de tableau:

- (NSString *)tableView:(UITableView *)tableView 
titleForHeaderInSection:(NSInteger)section {

if (!(section == 0 && [self.tableView numberOfSections] == 1)) {
id <NSFetchedResultsSectionInfo> sectionInfo =
[[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo name];
}

return nil;
}

C'est une méthode très générique qui va utiliser les valeurs du contrôleur chercher les résultats. Cette méthode devient essentiellement un petit copier-coller de code que vous pouvez utiliser n'importe quel contrôleur inchangée en qui utilise un contrôleur chercher les résultats avec les sections.

Si vous exécutez votre application maintenant, cependant, vous allez obtenir une section distincte pour chaque ligne (il serait également crash, mais nous nous en occuperons aussi bien). Dans la version que vous avez maintenant, nous avons spécifié le nom ou secretIdentity que notre sectionNameKeyPath, de sorte que chaque nom unique ou de l'identité secrète devient sa propre section. En règle générale, pas ce que veux. Alors, la prochaine étape est d'ajouter de méthode d'accès virtuel à notre objet Héros de retourner la valeur que nous voulons utiliser à utiliser pour diviser la liste des héros en place. Faisons-le par ordre alphabétique, ce qui signifie que nous avons besoin pour créer des méthodes pour retourner la première lettre du nom et de l'identité secrète. Nous pouvons ensuite utiliser ces nouvelles méthodes accesseur virtuel comme nos chemins section nom de la clé, et le contrôleur de chercher les résultats seront divisons la liste par la première lettre.

In Hero.h, Ajoutez les deux déclarations de méthode, juste avant le mot-clé end @:

- (NSString *)nameFirstLetter;
- (NSString *)secretIdentityFirstLetter;

Save Hero.h et de passer à Hero.mEt insérez la mise en œuvre de ces deux méthodes, au-dessus du mot-clé end @ encore:

- (NSString *)nameFirstLetter {
return [self.name substringToIndex:1];
}

- (NSString *)secretIdentityFirstLetter {
return [self.secretIdentity substringToIndex:1];
}

Save Hero.m. Ensuite, nous devons faire quelques changements dans HeroListViewController.m. Tout d'abord, changement d'affectation de sectionKey pour refléter notre nouvelle méthodes d'accès virtuel. Recherchez le code suivant dans la méthode fetchedResultsController et modifiez les lignes en gras pour passer de l'aide au nom et secretIdentity utilisant notre nouvelle première lettre de méthodes:

...
case kByName: {
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"secretIdentity" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
sectionKey = @"nameFirstLetter";
break;
}

case kBySecretIdentity:{
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:@"secretIdentity" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
sectionKey = @"secretIdentityFirstLetter";
break;
}

...


C'est essentiellement cela. Eh bien, non vraiment. Dans les tests, j'ai trouvé qu'il s'est crashé. Pour cette nouvelle version, nous avons besoin de modifier les méthodes de chercher des résultats déléguée contrôleur qui nous vous avons donnés au chapitre 2. Remplacez vos implémentations existantes du contrôleur: didChangeSection: atIndex: forChangeType: et le contrôleur: didChangeObject: atIndexPath: forChangeType: newIndexPath: avec les versions suivantes de nouvelles:

- (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 != 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];
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)
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:[newIndexPath section]] withRowAnimation:UITableViewRowAnimationNone];
else
if (tableSectionCount > 1)
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:[indexPath section]] withRowAnimation:UITableViewRowAnimationNone];


[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;
}

}

- (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];
break;
case NSFetchedResultsChangeDelete:
if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) ))
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
break;
case NSFetchedResultsChangeUpdate:
break;
default:
break;
}

}

Depuis notre demande initiale a donné tous les rangs de sa propre section, nous n'avons pas eu ce problème, mais ce qui arrive, c'est quand nous recevons un avis qu'une ligne a été déplacée, nous devons faire en sorte que le nombre de sections dans la table et faire correspondre contrôleur. Il est possible que suite déménagé à une nouvelle section, voire provoquer une rangée pour être supprimé ou inséré dans le contrôleur, mais pas la table. Si une ligne est déplacé d'une section à l'autre, et nous ne sommes pas en rendre compte, l'application se bloque. Si la ligne avait été déplacé de la dernière rangée dans une section, nous avons besoin de supprimer la section de la table, sauf si elle a été la dernière section, car les tables doivent avoir au moins un article. Si le déplacement de la ligne a créé une nouvelle section dans le contrôleur, nous insérons une nouvelle ligne dans la table.

Et c'est vraiment, vous avez maintenant la liste de votre héros SuperDB de diviser par la première lettre soit le héros le nom ou l'identité secrète, selon l'onglet a été sélectionné.

Vous devez utiliser les méthodes chercher les résultats déléguée contrôleur à partir de cette annonce au lieu de ceux dans le livre. Ceux dans le livre très bien fonctionner pour l'application dans le livre, mais celui-ci est plus souple et gérera une grande variété de situations correctement.

Aucun commentaire: