Прокрутка Jerky после обновления UITableViewCell с помощью UITableViewAutomaticDimension
Я создаю приложение, которое имеет представление фида для отправленных пользователем сообщений. Это представление имеет UITableView
с пользовательской реализацией UITableViewCell
. Внутри этой ячейки у меня есть еще один UITableView
для отображения комментариев. Суть такова:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
Первоначальный канал будет загружен с 3 комментариями для предварительного просмотра, но если есть больше комментариев или если пользователь добавляет или удаляет комментарий, я хочу обновить PostCell
на месте внутри представления таблицы фидов, добавив или удалив CommentCells
в таблицу комментариев внутри PostCell
. В настоящее время я использую следующий помощник, чтобы выполнить это:
// (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 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
}
Это прекрасно подходит для обновления. Сообщение обновляется на месте с комментариями, добавленными или удаленными внутри PostCell
, как ожидалось. Я использую автоматическое определение размера PostCells
в таблице подачи. Таблица комментариев PostCell
расширяется, чтобы отображать все комментарии, но анимация немного отрывистая, а таблица сортирует прокрутки вверх и вниз на дюжину пикселей или около того, пока происходит анимация обновления ячейки.
Перескакивание при изменении размера немного раздражает, но моя основная проблема возникает позже. Теперь, если я прокручу вниз в фиде, прокрутка будет гладкой, как раньше, но если я прокручу вверх над ячейкой, я просто изменил размер после добавления комментариев, фид несколько раз скачет назад, прежде чем он достигнет вершины фида. Я настраиваю iOS8
автоматические ячейки прорисовки для Feed следующим образом:
// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
Если я удалю estimatedRowHeight
, таблица просто прокручивается в начало в любое время, когда изменяется высота ячейки. Я чувствую себя довольно застрявшим на этом сейчас и как новый разработчик iOS, мог бы использовать любые советы, которые у вас могут быть.
Ответы
Ответ 1
Вот лучшее решение, которое я нашел для решения этой проблемы (проблема прокрутки + reloadRows + iOS 8 UITableViewAutomaticDimension);
Он состоит из сохранения каждой высоты словаря и обновления (в словаре), поскольку tableView отображает ячейку.
Затем вы вернете сохраненную высоту в методе - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
.
Вы должны реализовать что-то вроде этого:
Objective-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)
}
Ответ 2
У нас была та же проблема. Это происходит из-за плохой оценки высоты ячейки, которая заставляет SDK заставлять плохую высоту, которая вызовет скачок клеток при прокрутке назад. В зависимости от того, как вы построили свою ячейку, лучший способ исправить это - реализовать метод UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
Пока ваша оценка довольно близка к реальной стоимости высоты ячейки, это почти отменяет прыжки и рывок. Здесь, как мы его реализовали, вы получите логику:
- (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;
}
}
Добавлена поддержка 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
}
Ответ 3
dosdos ответ работал у меня в Swift 2
Объявить ivar
var heightAtIndexPath = NSMutableDictionary()
в func viewDidLoad()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Затем добавьте следующие 2 метода:
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 = NSMutableDictionary()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let height = self.heightAtIndexPath.object(forKey: indexPath)
if ((height) != nil) {
return CGFloat(height as! CGFloat)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
Ответ 4
У меня тоже была проблема. Я нашел обходное решение, но он не полностью исправил рывок. Но это кажется намного лучше, чем предыдущая изменчивая прокрутка.
В методе делегата UITableView
:cellForRowAtIndexPath:
попробуйте использовать следующие два метода для обновления ограничений перед возвратом ячейки. (Язык Swift)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
РЕДАКТИРОВАТЬ: Вам может понадобиться играть со значением tableView.estimatedRowHeight
, чтобы получить более плавную прокрутку.
Ответ 5
После ответа @dosdos.
Я также нашел интересным реализовать: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Специально для моего кода, где ячейка динамически меняет ограничения, пока ячейка уже отображается на экране. Обновление такого словаря помогает во второй раз отображать ячейку.
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
}
}
Ответ 6
Решение @dosdos работает нормально
но есть что-то, что вы должны добавить
после ответа @dosdos
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)
}
затем используйте эти строки, когда захотите, для меня я использую его внутри textDidChange
- первая перезагрузка Tableview
- ограничение обновления
-
наконец перейдите в начало Tableview
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)