web-dev-qa-db-fra.com

Existe-t-il un moyen de mettre à jour la hauteur d'un seul UITableViewCell, sans recalculer la hauteur pour chaque cellule?

J'ai un UITableView avec quelques sections différentes. Une section contient des cellules qui seront redimensionnées lorsqu'un utilisateur tape du texte dans un UITextView. Une autre section contient des cellules qui rendent le contenu HTML, pour lequel le calcul de la hauteur est relativement coûteux.

À l'heure actuelle, lorsque l'utilisateur tape dans UITextView, pour que la vue tabulaire mette à jour la hauteur de la cellule, j'appelle

[self.tableView beginUpdates];
[self.tableView endUpdates];

Cependant, la table recalcule la hauteur de chaque cellule dans la table, alors que je n'ai vraiment besoin que de mettre à jour la cellule single dans laquelle elle a été saisie. De plus, au lieu de recalculer la hauteur estimée à l'aide de tableView:estimatedHeightForRowAtIndexPath:, il appelle tableView:heightForRowAtIndexPath: pour chaque cellule, même celles qui ne sont pas affichées.

Existe-t-il un moyen de demander à la vue tabulaire de ne mettre à jour que la hauteur d'une cellule, sans effectuer tout ce travail inutile?

Mettre à jour

Je cherche toujours une solution à cela. Comme suggéré, j'ai essayé d'utiliser reloadRowsAtIndexPaths:, mais cela ne semble pas fonctionner. L'appel de reloadRowsAtIndexPaths: avec une seule ligne entraîne toujours l'appel de heightForRowAtIndexPath: pour chaque ligne, même si cellForRowAtIndexPath: ne sera appelé que pour la ligne que vous avez demandée. En fait, il semble que chaque fois qu'une ligne est insérée, supprimée ou rechargée, heightForRowAtIndexPath: est appelé pour chaque ligne de la cellule du tableau.

J'ai également essayé de mettre du code dans willDisplayCell:forRowAtIndexPath: pour calculer la hauteur juste avant qu'une cellule ne soit affichée. Pour que cela fonctionne, je devrais forcer la vue de table à redemander la hauteur de la ligne après le calcul. Malheureusement, l'appel de [self.tableView beginUpdates]; [self.tableView endUpdates]; à partir de willDisplayCell:forRowAtIndexPath: provoque une exception d'index hors limites profondément dans le code interne de UITableView. Je suppose qu'ils ne s'attendent pas à ce que nous fassions cela.

Je ne peux pas m'empêcher de penser que c'est un bogue dans le SDK qui, en réponse à [self.tableView endUpdates], n'appelle pas estimatedHeightForRowAtIndexPath: pour les cellules qui ne sont pas visibles, mais j'essaie toujours de trouver une solution de contournement. Toute aide est appréciée.

44
Chris Vasselli

Ce bogue a été corrigé dans iOS 7.1.

Dans iOS 7.0, il ne semble pas y avoir de solution à ce problème. L'appel de [self.tableView endUpdates] entraîne l'appel de heightForRowAtIndexPath: pour chaque cellule du tableau.

Cependant, dans iOS 7.1, l'appel de [self.tableView endUpdates] provoque l'appel de heightForRowAtIndexPath: pour les cellules visibles et de estimatedHeightForRowAtIndexPath: pour les cellules non visibles.

22
Chris Vasselli

Les hauteurs de rangées variables ont un impact très négatif sur les performances de l'affichage de votre table. Vous parlez de contenu Web affiché dans certaines cellules. Si nous ne parlons pas de milliers de lignes, réfléchir implémenter votre solution avec un UIWebView au lieu d'un UITableView pourrait être intéressant. Nous avons eu une situation similaire et est allé avec un UIWebView avec un balisage HTML généré personnalisé et cela a fonctionné à merveille. Comme vous le savez probablement, vous avez un problème asynchrone lorsque vous avez une cellule dynamique avec du contenu Web: 

  • Après avoir défini le contenu de la cellule, vous devez 
  • attendez que la vue Web de la cellule ait terminé de rendre le contenu Web, 
  • alors vous devez aller dans UIWebView et - en utilisant JavaScript - demander au document HTML quelle est sa hauteur 
  • et THEN met à jour la hauteur de UITableViewCell. 

Pas d'amusement du tout et beaucoup de sauts et de tremblements pour l'utilisateur.

Si vous devez utiliser une UITableView, définitivement cache la hauteur calculée des lignes. De cette façon, il sera bon marché de les renvoyer dans heightForRowAtIndexPath:. Au lieu de dire à UITableView quoi faire, créez simplement votre source de données rapidement. 

8

Y a-t-il un moyen? La réponse est non.

Vous ne pouvez utiliser que heightForRowAtIndexPath pour cela. Tout ce que vous pouvez faire, c’est donc de le rendre le moins coûteux possible, en conservant par exemple un tableau NSmutableArray de la hauteur de votre cellule dans votre modèle de données.

2
ader

J'ai eu un problème similaire (saut de défilement de la vue de table sur tout changement) parce que j'avais 

  • (CGFloat) tableView: (UITableView *) tableView EstimationHeightForRowAtIndexPath: (NSIndexPath *) indexPath {. retourne 500; }

commenter l'ensemble de la fonction a aidé.

2
soprof

Utilisez la méthode UITableView suivante:

- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

Vous devez spécifier un NSArray de NSIndexPath que vous souhaitez recharger. Si vous souhaitez recharger une seule cellule, vous pouvez fournir un NSArray contenant un seul NSIndexPath.

NSIndexPath* rowTobeReloaded = [NSIndexPath indexPathForRow:1 inSection:0];
NSArray* rowsTobeReloaded = [NSArray arrayWithObjects:rowTobeReloaded, nil];
[UITableView reloadRowsAtIndexPaths:rowsTobeReloaded withRowAnimation:UITableViewRowAnimationNone];
1
Nilesh Patel

J'ai fini par trouver un moyen de contourner le problème. J'ai pu pré-calculer la hauteur du contenu HTML que je dois restituer et l'inclure également dans la base de données. Ainsi, bien que je sois obligé de fournir la hauteur à toutes les cellules lorsque je mets à jour la hauteur d’une cellule, je n’ai pas à effectuer de rendu HTML coûteux, c’est donc très accrocheur.

Malheureusement, cette solution ne fonctionne que si vous avez tout votre contenu HTML au début.

0
Chris Vasselli

La méthode heightForRowAtIndexPath: sera toujours appelée, mais voici une solution de contournement que je suggérerais.

Chaque fois que l'utilisateur tape UITextView, enregistre dans une variable locale le indexPath du cellule. Ensuite, lorsque heightForRowAtIndexPath: est appelé, vérifiez la valeur de la indexPath enregistrée. Si la indexPath enregistrée n'est pas nil, récupérez le cellule cela devrait être redimensionné et le faire. Comme pour les autres cellules, utilisez vos valeurs mises en cache. Si la indexPath enregistrée est nil, exécutez vos lignes de code habituelles qui, dans votre cas, sont exigeantes.

Voici comment je recommanderais de le faire:

Utilisez la propriété tag of UITextView pour savoir quelle ligne doit être redimensionnée.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...

    [textView setDelegate:self];
    [textView setTag:indexPath.row];

    ...
}

Ensuite, dans la méthode UITextView de votre délégué textViewDidChange:, récupérez la indexPath et stockez-la. savedIndexPath est une variable locale.

- (void)textViewDidChange:(UITextView *)textView
{
    savedIndexPath = [NSIndexPath indexPathForRow:textView.tag inSection:0];
}

Enfin, vérifiez la valeur de savedIndexPath et exécutez ce qui est nécessaire.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (savedIndexPath != nil) {
        if (savedIndexPath == indexPath.row) {
            savedIndexPath = nil;

            // return the new height
        }
        else {

            // return cached value
        }
    }
    else {
        // your normal calculating methods...
    }
}

J'espère que ça aide! Bonne chance.

0
jbrodriguez