4
votes

Cellule de réduction de TableView lors du défilement vers une autre: comportement étrange

J'ai une tableView avec des bulles de chat.


Ces bulles sont raccourcies si le nombre de caractères est supérieur à 250

Si un utilisateur clique sur une bulle

  • La sélection précédente est désélectionnée (raccourcie)
  • La nouvelle sélection s'agrandit et révèle tout le contenu
  • La nouvelle contrainte de sélection supérieure change (de 0 à 4)

Qu'est-ce que je voudrais réaliser?

Si une longue bulle est déjà sélectionnée, mais que l'utilisateur sélectionne une autre bulle, je veux que tableView défile jusqu'à la position de la nouvelle bulle sélectionnée.

Je vais partager une vidéo à ce sujet

Sans ce défilement, le contentOffset reste le même et il semble mauvais.

(Dans la vidéo : à droite)


Vidéo:

Droite: sans le défilement mentionné

Gauche: avec défilement

https://youtu.be/_-peZycZEAE


Voici le problème:

Sur la gauche, vous pouvez remarquer que c'est un problème.

  • Des cellules fantômes aléatoires apparaissent sans raison.

  • Parfois, cela gâche même la hauteur de certaines bulles (pas dans la vidéo)

Pourquoi est-ce ainsi?

Code:

func bubbleTappedHandler(sender: UITapGestureRecognizer) {
        
        let touchPoint = sender.location(in: self.tableView)
        if let indexPath = tableView.indexPathForRow(at: touchPoint) {
            
            if indexPath == currentSelectedIndexPath {

                // Selected bubble is tapped, deselect it
                self.selectDeselectBubbles(deselect: indexPath)

            } else {
                if (currentSelectedIndexPath != nil){

                    // Deselect old bubble, select new one
                    self.selectDeselectBubbles(select: indexPath, deselect: currentSelectedIndexPath)
                    
                } else {

                    // Select bubble
                    self.selectDeselectBubbles(select: indexPath)

                }
            }

        }
    }
    
    
    
    func selectDeselectBubbles(select: IndexPath? = nil, deselect: IndexPath? = nil){
        
        
        var deselectCell : WorldMessageCell?
        var selectCell : WorldMessageCell?
        
        if let deselect = deselect {
            deselectCell = tableView.cellForRow(at: deselect) as? WorldMessageCell
        }
        if let select = select {
            selectCell = tableView.cellForRow(at: select) as? WorldMessageCell
        }
        
        
        // Deselect Main
        if let deselect = deselect, let deselectCell = deselectCell {

            tableView.deselectRow(at: deselect, animated: false)
            currentSelectedIndexPath = nil
            // Update text
            deselectCell.messageLabel.text = self.dataSource[deselect.row].message.shortened()

            
        }
        // Select Main
        if let select = select, let selectCell = selectCell {

            tableView.selectRow(at: select, animated: true, scrollPosition: .none)
            currentSelectedIndexPath = select
            // Update text
            deselectCell.messageLabel.text = self.dataSource[select.row].message.full()
        }
        
        
        UIView.animate(withDuration: appSettings.defaultAnimationSpeed) {
                
            // Deselect Constraint changes
            
            if let deselect = deselect, let deselectCell = deselectCell {
                // Constarint change
                deselectCell.nickNameButtonTopConstraint.constant = 0
                deselectCell.timeLabel.alpha = 0.0
                deselectCell.layoutIfNeeded()
                
            }
            
            // Select Constraint changes
            if let select = select, let selectCell = selectCell {
                
                // Constarint change
                selectCell.nickNameButtonTopConstraint.constant = 4
                selectCell.timeLabel.alpha = 1.0
                selectCell.layoutIfNeeded()
                
                
            }
            
            
        }

        self.tableView.beginUpdates()
        self.tableView.endUpdates()
        
        
        
        UIView.animate(withDuration: appSettings.defaultAnimationSpeed) {
            if let select = select, deselect != nil, self.tableView.cellForRow(at: deselect!) == nil && deselectCell != nil {

                // If deselected row is not anymore on screen
                // but was before the collapsing,
                // then scroll to new selected row  

                self.tableView.scrollToRow(at: select, at: .top, animated: false)
            }
        }

    }

Mise à jour 1: ajoutée Projet Github

Lien: https://github.com/ krptia / test2

J'ai créé une petite version de mon application, afin que vous puissiez voir et tester quel est mon problème. Je serais très reconnaissant si quelqu'un pouvait aider à résoudre ce problème! : c


4 commentaires

Lorsque vous faites défiler, il redessine les cellules et si vous ne gérez pas les cellules pour stocker l'état dans lequel elles se trouvaient, il dessinera l'état initial.


@carbonr merci pour votre réponse! Que puis-je faire à ce sujet pour le rendre fluide?: C


Une chose est importante ici. Vous utilisez un outil de reconnaissance de gestes pour la détection de sélection. La sélection de cellule est-elle activée sur la tableView et implémentez-vous également la méthode didSelectRow de UITableViewDelegate ?


@Adeel Selection est activé, mais je n'implémente pas la méthode didSelectRow . J'ai créé mon propre "système de sélection": parce que je veux que la sélection ne se produise que lorsque l'utilisateur clique exactement sur la bulle (c'est pourquoi j'ai créé un délégué (pour bubbleTappedHandler ), et pourquoi j'ai le Variable currentSelectedIndexPath . En fait, je n'ai pas besoin d'utiliser tableView.selectRow (at: ...) et tableView.deselectRow (at: ...) < / code>, alors oui, je vais simplement supprimer ces lignes.


5 Réponses :


0
votes

Essayez d'utiliser cette API willDisplay sur UITableViewDelegate

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if (indexPath == currentSelectedIndexPath) {
        // logic to expand your cell
        // you don't need to animate it since still not visible
    }
}


0 commentaires

0
votes

Remplacez votre code pour bubbleTappedHandler par ceci et exécutez et vérifiez:

func bubbleTappedHandler(sender: UITapGestureRecognizer) {

    let touchPoint = sender.location(in: self.tableView)
    if let indexPath = tableView.indexPathForRow(at: touchPoint) {

        if indexPath == currentSelectedIndexPath {
            currentSelectedIndexPath = nil
            tableView.reloadRows(at: [indexPath], with: .automatic)
        } else {
            if (currentSelectedIndexPath != nil){
                if let prevSelectedIndexPath = currentSelectedIndexPath {
                    currentSelectedIndexPath = indexPath
                    tableView.reloadRows(at: [prevSelectedIndexPath, indexPath], with: .automatic)
                }
            } else {
                currentSelectedIndexPath = indexPath
                tableView.reloadRows(at: [indexPath], with: .automatic)
            }

        }

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { [weak self] in
            let currentIndexPath = self?.currentSelectedIndexPath ?? indexPath
            self?.tableView.scrollToRow(at: currentIndexPath, at: .top, animated: false)
        })
    }
}


2 commentaires

Merci pour la réponse, mais cela ne résout pas le problème: c


L'animation n'est pas fluide, encore plus floue avec la méthode reloadRows que vous avez mentionnée



0
votes

Vous pouvez utiliser tableView? .scrollToRow . Par exemple

let newIndexPath = IndexPath(row: 0, section: 0) // put your selected indexpath and section here 
yourTableViewName?.scrollToRow(at: newIndexPath, at: UITableViewScrollPosition.top, animated: true) // give the desired scroll position to tableView 

Les positions de défilement disponibles sont .aucun , .top , .middle , .bottom


0 commentaires

0
votes

Ce problème est dû à une petite erreur. Faites simplement ces 2 choses et le problème sera résolu.

  1. Définissez les paramètres rowHeight et estimeRowHeight de tableView dans `viewDidLoad

    tableView.estimatedRowHeight = 316 // Value taken from your project. This may be something else
    tableView.rowHeight = UITableView.automaticDimension
    
  2. Supprimez les méthodes déléguées EstimedHeightForRowAt et heightForRowAt de votre code.


1 commentaires

J'ai essayé ce que vous avez écrit, mais cela pose toujours le problème. J'ai mis à jour le Github avec votre solution, vérifiez-le: cliquez sur le long, puis faites défiler vers le bas (pour que le long soit toujours visible en haut, mais le plus petit aussi): et cliquez sur le plus petit. C'est étrangement animé



2
votes

Commençons par définir ce que nous entendons par "sans défilement" - nous voulons dire que les cellules restent plus ou moins les mêmes. Nous voulons donc trouver une cellule que nous voulons être la cellule d'ancrage. D'avant les changements à après les changements, les distances entre le haut de la cellule et le haut de l'écran sont les mêmes.

  self.tableView.beginUpdates()
  self.tableView.endUpdates()
  self.tableView.layoutSubviews()
  self.scrollToAnchorPoint()

nous appelons cela avant de commencer à faire des choses.

func scrollToAnchorPoint() {
  if let indexPath = indexPathAnchorPoint, let offset = offsetAnchorPoint {
    let rect = self.tableView.rectForRow(at: indexPath)
    let contentOffset = rect.origin.y - offset
    self.tableView.setContentOffset(CGPoint.init(x: 0, y: contentOffset), animated: false)
  }
}

Ensuite, nous devons définir le décalage du contenu après avoir effectué nos modifications afin que la cellule revienne là où elle est supposée.

 func bubbleTappedHandler(sender: UITapGestureRecognizer) {
    self.setAnchorPoint()
     ....

Ensuite, nous l'appelons après avoir fait nos changements.

var indexPathAnchorPoint: IndexPath?
var offsetAnchorPoint: CGFloat?

func findHighestCellThatStartsInFrame() -> UITableViewCell? {
  return self.tableView.visibleCells.filter {
     $0.frame.origin.y >= self.tableView.contentOffset.y
  }.sorted {
     $0.frame.origin.y > $1.frame.origin.y
  }.first
}

func setAnchorPoint() {
  self.indexPathAnchorPoint = nil;
  self.offsetAnchorPoint = nil;

  if let cell = self.findHighestCellThatStartsInFrame() {
    self.offsetAnchorPoint = cell.frame.origin.y - self.tableView.contentOffset.y
    self.indexPathAnchorPoint = self.tableView.indexPath(for: cell)
  }
}

L'animation peut être un peu étrange car il se passe beaucoup de choses en même temps. Nous modifions le décalage du contenu et les tailles de la cellule en même temps, mais si vous placez votre doigt à côté de la première cellule dont le haut est visible, vous verrez qu'elle se termine au bon endroit.

p >


2 commentaires

Cela fonctionne très bien! Merci pour votre réponse! Oui, l'animation est un peu étrange mais c'est le meilleur qui puisse être réalisé, merci beaucoup! Une question: la tableView saute pendant une seconde lorsqu'une longue bulle est désélectionnée en haut et qu'une plus petite est sélectionnée en dessous. Est-il possible de réparer le saut en rechargeant simplement la tableView? J'expérimente.


Je ne sais pas. Quelque chose que vous pouvez essayer de ralentir l'animation dans toute l'application afin que vous puissiez voir exactement ce que fait l'animation. UIApplication.shared.keyWindow? .Layer.speed = 0,5