web-dev-qa-db-fra.com

iOS 8 Hauteur de cellule automatique - Impossible de faire défiler jusqu'à la dernière ligne

J'utilise iOS 8 nouvelles cellules à auto-dimensionnement. Visuellement, cela fonctionne bien - chaque cellule a la bonne taille. Cependant, si j'essaie de faire défiler jusqu'à la dernière ligne, la vue du tableau ne semble pas connaître sa taille correcte. Est-ce un bug ou existe-t-il un correctif pour cela?

Voici comment recréer le problème:

En utilisant ce projet - TableViewCellWithAutoLayoutiOS8 (référencé à partir de this SO answer ), j'ai obtenu le redimensionnement automatique des cellules comme prévu. 

Cependant, si j'appelle la fonction scrollToRowAtIndexPath, comme ceci:

tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)

Je ne vais pas à la dernière rangée - Cela ne me fait faire la moitié du chemin. 

Même en essayant d'utiliser une fonction de niveau inférieur comme ceci:

tableView.setContentOffset(CGPointMake(0, tableView.contentSize.height - tableView.frame.size.height), animated: true)

Le résultat n'est pas comme prévu, il ne va pas arriver à la fin. Si je clique dessus souvent ou si j'attends quelques instants, cela finira par arriver au bon endroit. Il semble que tableView.contentSize.height ne soit pas défini correctement, de sorte que l'iOS "ne sait pas" où se trouve cette dernière cellule.

J'apprécierais toute aide.

Merci

56
BenB

Mise à jour: 24 juin 2015

Apple a corrigé la plupart de ces problèmes depuis le SDK iOS 9.0. Tous les problèmes sont corrigés à partir de iOS 9 bêta 2, notamment le défilement vers le haut et le bas de la vue du tableau sans animation et l'appel de reloadData lors du défilement au milieu de la vue du tableau. 

Voici les problèmes restants qui n'ont pas encore été résolus:

  1. Lorsque vous utilisez une hauteur de ligne estimée élevée, le défilement jusqu'à la dernière ligne avec animation entraîne la disparition des cellules de la vue de tableau.
  2. Lorsque vous utilisez une petite hauteur de ligne estimée, le défilement jusqu'à la dernière ligne avec animation entraîne la fin du défilement trop tôt dans la vue Tableau, laissant certaines cellules sous la zone visible (et la dernière ligne encore hors écran).

Un nouveau rapport de bogue (rdar: // 21539211) a été déposé pour ces problèmes liés au défilement avec animation.

Réponse originale

Il s’agit d’un bogue Apple lié à l’estimation de la hauteur des rangées de la vue tableau. Il existe depuis que cette fonctionnalité a été introduite dans iOS 7. J’ai travaillé directement avec les ingénieurs et les développeurs de développeurs UIKit d’Apple sur cette question. bug, mais ne dispose pas de solution de contournement fiable (à moins de désactiver l’estimation de la hauteur des rangées) et ne semble pas particulièrement intéressé à le corriger.

Notez que le bogue se manifeste de différentes manières, par exemple en faisant disparaître les cellules de la vue du tableau lorsque vous appelez reloadData tout en faisant défiler partiellement ou totalement vers le bas (par exemple, contentOffset.y est nettement supérieur à 0).

Clairement, avec iOS 8 cellules auto-dimensionnantes, l’estimation de la hauteur des rangées revêt une importance cruciale; Apple doit donc s’attaquer à cette question dès que possible.

J'ai classé ce problème le 21 octobre 2013 sous le nom de Radar # 15283329. Merci de classer les rapports de bogues en double afin que Apple donne la priorité à un correctif.

Vous pouvez joindre ce simple exemple de projet pour illustrer le problème. Il est basé directement sur le propre exemple de code d'Apple.

32
smileyborg

Cela a été un bug très agaçant, mais je pense avoir trouvé une solution permanente, bien que je ne puisse pas expliquer pourquoi. 

Appelez la fonction après un petit délai (inaperçu):

let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

dispatch_after(time, dispatch_get_main_queue(), {
  tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)
})

Dites-moi si cela fonctionne pour vous aussi.

20
abinop

C'est certainement un bug d'Apple. J'ai aussi ce problème. J'ai résolu ce problème en appelant la méthode "scrollToRowAtIndexPath" à deux reprises. Le code exemple est:

        if array.count > 0 {
        let indexPath: NSIndexPath = NSIndexPath(forRow: array.count - 1, inSection: 0)
        self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
        let delay = 0.1 * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

        dispatch_after(time, dispatch_get_main_queue(), {
            self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
        })
    }
13
Bhavesh Tiwari

J'ai trouvé une solution temporaire qui pourrait être utile jusqu'à ce qu'Apple décide de corriger les nombreux bogues qui nous sévissent.

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *text = [self findTextForIndexPath:indexPath];
    UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:13];
    CGRect estimatedHeight = [text boundingRectWithSize:CGSizeMake(215, MAXFLOAT)
                                                options:NSStringDrawingUsesLineFragmentOrigin
                                             attributes:@{NSFontAttributeName: font}
                                                context:nil];
    return TOP_PADDING + CGRectGetHeight(estimatedHeight) + BOTTOM_PADDING;
}

Ce n'est pas parfait, mais cela a fonctionné pour moi. Maintenant je peux appeler:

- (void)scrollToLastestSeenMessageAnimated:(BOOL)animated
{
    NSInteger count = [self tableView:self.tableView numberOfRowsInSection:0];
    if (count > 0) {
        NSInteger lastPos = MAX(0, count-1);
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:lastPos inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
    }
}

Sur viewDidLayoutSubviews et il trouve la bonne place en bas (ou une position estimée très proche).

J'espère que ça aide.

4
Guilherme Sprint

Pour mon cas, j'ai trouvé une solution temporaire en ne suggérant pas une hauteur de cellule estimée au programme. Je l'ai fait en commentant la méthode suivante dans mon code:

- (CGFloat) tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

Cependant, veuillez noter que cela peut affecter l'expérience utilisateur lorsque l'utilisateur défile, si vos cellules varient beaucoup les unes des autres. Pour mon cas, pas de différence notable jusqu'à présent.

J'espère que ça aide!

1
Marcus

Ma solution consistait à utiliser la taille du scénarimage comme estimation. 

Donc au lieu de cela:

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

}

J'ai fait quelque chose comme ça:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 

MyMessageType messageType = [self messageTypeForRowAtIndexPath:indexPath];

switch (messageType) {

    case MyMessageTypeText:
        return 45;
        break;

    case MyMessageTypeMaybeWithSomeMediaOrSomethingBiggerThanJustText:
        return 96;
        break;

    default:
        break;
 }
}

J'écris en mode table de discussion, il est donc probable que bon nombre de mes cellules, en particulier ce type de texte, soient plus volumineuses que celles contenues dans IB, notamment si le message de discussion est très long. Cela semble être un assez bon ... bien ... estimation et le défilement vers le bas devient assez proche. Cela semble être légèrement pire à mesure que le défilement s'allonge, mais je suppose que c'est à prévoir

0
Roderic Campbell

J'ai eu le même problème lors de la création d'une table de discussion avec une hauteur de cellules différente. J'appelle le code ci-dessous dans la méthode viewDidAppear () lifecycle:

// First figure out how many sections there are
let lastSectionIndex = self.tableView.numberOfSections - 1

// Then grab the number of rows in the last section
let lastRowIndex = self.tableView.numberOfRowsInSection(lastSectionIndex) - 1

// Now just construct the index path
let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)

// Make the last row visible
self.tableView.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)

S'il vous plaît laissez-moi savoir si cela a fonctionné pour vous aussi.

0
Stefan Olaru

Dans la fenêtre du storyboard, cliquez dans une zone vide pour désélectionner toutes les vues, cliquez sur la vue contenant la vue tableau, puis cliquez sur l'icône Resolve Auto Layout Issue et sélectionnez Reset to Suggested Constraints.

enter image description hereenter image description here

0
kendotwill

Bien que comme réponse de smileyborg il s'agit d'un bogue dans iOS 8.x, il devrait être corrigé sur toutes les plateformes que vous prenez en charge ...

Pour résoudre ce problème sur les versions antérieures à iOS9, le code ci-dessous permet de résoudre le problème sans dispatch_async ou dispatch_after . Testé sur un simulateur iOS 8.4.

UPDATE: appeler (uniquement) layoutIfNeeded ne fonctionne pas lorsque le contrôleur de vue devient visible en faisant défiler UIPageViewController. Utilisez donc layoutSubviews (ou peut-être setNeedsLayout + layoutIfNeeded) à la place.

// For iOS 8 bug workaround.
// See https://stackoverflow.com/a/33515872/1474113
- (void)scrollToBottomForPreiOS9
{
    CGFloat originalY, scrolledY;
    do {
        // Lay out visible cells immediately for current contentOffset.
        // NOTE: layoutIfNeeded does not work when hosting UIPageViewController is dragged.
        [self.tableView layoutSubviews];
        originalY = self.tableView.contentOffset.y;
        [self scrollToBottom];  // Call -scrollToRowAtIndexPath as usual.
        scrolledY = self.tableView.contentOffset.y;
    } while (scrolledY > originalY);
}
0
ypresto

Utilisez ce code simple pour faire défiler le bas

 var rows:NSInteger=self.tableName.numberOfRowsInSection(0)
        if(rows > 0)
        {
            let indexPath = NSIndexPath(forRow: rows-1, inSection: 0)
            tableName.scrollToRowAtIndexPath(indexPath , atScrollPosition:  UITableViewScrollPosition.Bottom, animated: true)
        }
            }
0
Bibin Joseph

Il suffit d'appeler tableview reloadData après que viewDidAppear peut résoudre le problème

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.tableView reloadData];
}
0
Evan JIANG