web-dev-qa-db-fra.com

Comment conserver UITableView contentoffset après avoir appelé -reloadData

CGPoint offset = [_table contentOffset];
[_table reloadData];
[_table setContentOffset:offset animated:NO];    //unuseful

//    __block UITableView *tableBlock = _table;
//    [self performBlock:^(id sender) {
//        [tableBlock setContentOffset:offset];
//    } afterDelay:2];

Je sais que je ne connais aucune méthode déléguée qui soit appelée après reloadData. Et en utilisant afterDelay:2 qui est une sorte de hack peut être trop court ou trop long, alors comment puis-je l'implémenter?

56
avincross

Je travaillais récemment avec reloadData - reloadData ne modifie pas le contentOffset ni ne fait défiler la vue du tableau. Il reste en fait le même si le décalage est inférieur à la nouvelle quantité de données.

14
Louis

J'avais des problèmes avec cela parce que je joue avec le dimensionnement des cellules dans ma méthode cellForRowAtIndexPath. J'ai remarqué que les informations de dimensionnement étaient désactivées après avoir fait reloadData, alors j'ai réalisé que je devais le forcer à la mise en page immédiatement avant de rétablir le décalage de contenu.

CGPoint offset = tableView.contentOffset;
[tableView.messageTable reloadData];
[tableView layoutIfNeeded]; // Force layout so things are updated before resetting the contentOffset.
[tableView setContentOffset:offset];
105
Matt Koala

L'appel de reloadData sur la tableView ne modifie pas l'offset de contenu. Cependant, si vous utilisez UITableViewAutomaticDimension qui a été introduit dans iOS 8, vous pourriez avoir un problème.

En utilisant UITableViewAutomaticDimension, il faut écrire la méthode déléguée tableView: estimatedHeightForRowAtIndexPath: et renvoyer UITableViewAutomaticDimension avec tableView: heightForRowAtIndexPath: qui renvoie également la même chose.

Pour moi, j'ai eu des problèmes dans iOS 8 lors de l'utilisation de cela. C'est parce que la méthode estimatedHeightForRowAtIndexPath: la méthode renvoyait des valeurs inexactes même si j'utilisais UITableViewAutomaticDimension. C'était un problème avec iOS 8 car il n'y avait aucun problème avec les appareils iOS 9.

J'ai résolu ce problème en utilisant un dictionnaire pour stocker la valeur de la hauteur de la cellule et la renvoyer. C'est ce que j'ai fait.

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *key = @(indexPath.row);
    NSNumber *height = @(cell.frame.size.height);

    [self.cellHeightsDictionary setObject:height forKey:key];
}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *key = @(indexPath.row);
    NSNumber *height = [self.cellHeightsDictionary objectForKey:key];

    if (height)
    {
        return height.doubleValue;
    }

    return UITableViewAutomaticDimension;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewAutomaticDimension;
}

La vérification de la hauteur existe pour les premiers chargements de page.

86
Skywalker

Variante Swift 4 de @Skywalker:

fileprivate var heightDictionary: [Int : CGFloat] = [:]

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    heightDictionary[indexPath.row] = cell.frame.size.height
}

override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    let height = heightDictionary[indexPath.row]
    return height ?? UITableViewAutomaticDimension
}

Une autre solution (récupérée à partir de MessageKit):

Cette méthode doit être appelée au lieu de reloadData. Cela peut convenir à des cas spécifiques.

public func reloadDataAndKeepOffset() {
    // stop scrolling
    setContentOffset(contentOffset, animated: false)

    // calculate the offset and reloadData
    let beforeContentSize = contentSize
    reloadData()
    layoutIfNeeded()
    let afterContentSize = contentSize

    // reset the contentOffset after data is updated
    let newOffset = CGPoint(
        x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
        y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
    setContentOffset(newOffset, animated: false)
}
30
Nik Kov

Par défaut, reloadData conserve le contentOffset. Cependant, il pourrait être mis à jour si vous avez des valeurs d'estimationRowHeight inexactes.

23
Cyril

Dans mon cas, décochez la hauteur de ligne automatique et estimez le problème automatique résolu

fix reload problem

18
saranpol

Si vous implémentez la méthode estimatedHeightForRowAtIndexPath et que votre estimation n'est pas correcte, vous risquez de vous retrouver dans cette situation.

Pour résoudre ce problème, vous pouvez renvoyer une grande hauteur plus grande que chaque hauteur de cellule dans votre tableView, comme ceci:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return 800.f; // if 800 is bigger than every possible value of your cell height.
}
10
bingxin xue

Si vous insérez des données au début de votre tableau dataSource, vous devez modifier contentOffset comme ceci: Swift 3 +

func prepareReloadData() {
    let previousContentHeight = tableView.contentSize.height
    let previousContentOffset = tableView.contentOffset.y
    tableView.reloadData()
    let currentContentOffset = tableView.contentSize.height - previousContentHeight + previousContentOffset
    tableView.contentOffset = CGPoint(x: 0, y: currentContentOffset)
}
7
Bohdan Savych

@ La réponse de Skywalker a montré la meilleure solution pour le problème de hauteur estimée des cellules. Mais parfois, le problème réside dans un endroit différent.
Parfois, le problème réside dans contentInsets of table view. Si vous effectuez un rechargement des données alors que tableView n'est pas visible à l'écran, vous pouvez faire face à un décalage incorrect une fois que la vue de table apparaît à l'écran.
.
J'ai rencontré ce problème dans iOS 9.1

2
Andrew Romanov

J'ai eu le même problème mais aucune des réponses suggérées ici n'a fonctionné. Voici comment je l'ai résolu. Sous-classe UITableView et substitution layoutSubviews méthode comme ceci:

override func layoutSubviews() {
    let offset = contentOffset
    super.layoutSubviews()
    contentOffset = offset
}
2
Mikhail Vasilyev