Je construis une application avec une vue de fil pour les publications soumises par les utilisateurs. Cette vue a une UITableView
avec une implémentation UITableViewCell
personnalisée. Dans cette cellule, j’ai une autre UITableView
pour afficher les commentaires. The Gist est quelque chose comme ceci:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
Le flux initial sera téléchargé avec 3 commentaires pour la prévisualisation, mais s'il y a plus de commentaires, ou si l'utilisateur ajoute ou supprime un commentaire, je souhaite mettre à jour la variable PostCell
à la place de la vue de la table des flux en ajoutant ou en supprimant CommentCells
aux commentaires. table à l'intérieur de la PostCell
. J'utilise actuellement l'aide suivante pour accomplir cela:
// (PostCell.Swift) Handle showing/hiding comments
func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {
let table = self.superview?.superview as UITableView
// "table" is outer feed table
// self is the PostCell that is updating it's comments
// self.comments is UITableView for displaying comments inside of the PostCell
table.beginUpdates()
self.comments.beginUpdates()
// This function handles inserting/removing/reloading a range of comments
// so we build out an array of index paths for each row that needs updating
var indexPaths = [NSIndexPath]()
for var index = startRow; index <= endRow; index++ {
indexPaths.append(NSIndexPath(forRow: index, inSection: 0))
}
switch operation {
case .INSERT:
self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .DELETE:
self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .RELOAD:
self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
self.comments.endUpdates()
table.endUpdates()
// trigger a call to updateConstraints so that we can update the height constraint
// of the comments table to fit all of the comments
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height
}
Ceci accomplit la mise à jour très bien. La publication est mise à jour sur place avec des commentaires ajoutés ou supprimés à l'intérieur de la PostCell
comme prévu. J'utilise le redimensionnement automatique PostCells
dans le tableau des flux. La table de commentaires de la variable PostCell
se développe pour afficher tous les commentaires, mais l'animation est un peu saccadée et la sorte de table défile vers le haut et vers le bas d'une dizaine de pixels environ pendant que l'animation de mise à jour de cellule a lieu.
Le saut lors du redimensionnement est un peu gênant, mais mon problème principal vient ensuite. Maintenant, si je fais défiler le flux vers le bas, le défilement est lisse comme avant, mais si je fais défiler au-dessus de la cellule que je viens de redimensionner après avoir ajouté des commentaires, le flux basculera plusieurs fois en arrière avant d'atteindre le sommet du flux. J'ai configuré les cellules de redimensionnement automatique iOS8
comme suit:
// (FeedController.Swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
Si je supprime la estimatedRowHeight
, le tableau défile vers le haut chaque fois qu'une hauteur de cellule change. Je me sens assez coincé là-dessus maintenant et en tant que nouveau développeur iOS, je pourrais utiliser vos conseils.
Voici la meilleure solution que j'ai trouvée pour résoudre ce genre de problème (problème de défilement + reloadRows + iOS 8 UITableViewAutomaticDimension);
Cela consiste à conserver toutes les hauteurs dans un dictionnaire et à les mettre à jour (dans le dictionnaire), car la vue tableau affichera la cellule.
Vous retournerez ensuite la hauteur sauvegardée dans la méthode - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
.
Vous devriez implémenter quelque chose comme ceci:
Objectif c
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height) {
return height.floatValue;
} else {
return UITableViewAutomaticDimension;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = @(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
Swift 3
@IBOutlet var tableView : UITableView?
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
Nous avons eu le même problème. Cela provient d'une mauvaise estimation de la hauteur de la cellule qui force le SDK à forcer une hauteur incorrecte, ce qui provoque le saut de cellules lors du défilement. Selon la manière dont vous avez construit votre cellule, la meilleure solution consiste à implémenter la méthode UITableViewDelegate
. - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
Tant que votre estimation est assez proche de la valeur réelle de la hauteur de la cellule, cela annulera presque le saut et les secousses. Voici comment nous l'avons implémenté, vous obtiendrez la logique:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method will get your cell identifier based on your data
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kFirstCellIdentifier])
return kFirstCellHeight;
else if ([cellType isEqualToString:kSecondCellIdentifier])
return kSecondCellHeight;
else if ([cellType isEqualToString:kThirdCellIdentifier])
return kThirdCellHeight;
else {
return UITableViewAutomaticDimension;
}
}
Ajout du support Swift 2
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// This method will get your cell identifier based on your data
let cellType = reuseIdentifierForIndexPath(indexPath)
if cellType == kFirstCellIdentifier
return kFirstCellHeight
else if cellType == kSecondCellIdentifier
return kSecondCellHeight
else if cellType == kThirdCellIdentifier
return kThirdCellHeight
else
return UITableViewAutomaticDimension
}
la réponse dosdos a fonctionné pour moi dans Swift 2
Déclarer l'ivar
var heightAtIndexPath = NSMutableDictionary()
dans func viewDidLoad ()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Ajoutez ensuite les 2 méthodes suivantes:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = self.heightAtIndexPath.objectForKey(indexPath)
if ((height) != nil) {
return CGFloat(height!.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath)
}
Swift 3:
var heightAtIndexPath = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}
Je faisais face au même problème aussi. J'ai trouvé une solution de contournement, mais cela ne résout pas complètement le crétin. Mais il semble être beaucoup mieux comparé au défilement désordonné précédent.
Dans votre méthode de délégué UITableView
:cellForRowAtIndexPath:
, essayez d'utiliser les deux méthodes suivantes pour mettre à jour les contraintes avant de renvoyer la cellule. (Langue rapide)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDIT: Vous devrez peut-être aussi jouer avec la valeur tableView.estimatedRowHeight
pour obtenir un défilement plus fluide.
Après @dosdos answer.
J'ai aussi trouvé intéressant d'implémenter: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Spécialement pour mon code, où la cellule modifie les contraintes de manière dynamique alors que la cellule est déjà affichée à l'écran. La mise à jour du dictionnaire comme ceci aide la deuxième fois que la cellule est affichée.
var heightAtIndexPath = [NSIndexPath : NSNumber]()
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = UITableViewAutomaticDimension
....
extension TableViewViewController: UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = heightAtIndexPath[indexPath]
if let height = height {
return CGFloat(height)
}
else {
return UITableViewAutomaticDimension
}
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
}
La solution @dosdos fonctionne bien
mais il y a quelque chose que vous devriez ajouter
suite à @dosdos answer
Swift 3/4
@IBOutlet var tableView : UITableView!
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
puis utilisez ces lignes quand vous le voulez, je l’utilise à l’intérieur de textDidChange
enfin passer en haut de la table
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)