Я чувствую, что это может быть обычная проблема, и мне было интересно, есть ли какое-то общее решение.
По сути, мой UITableView имеет динамическую высоту ячеек для каждой ячейки. Если я не нахожусь в верхней части UITableView и я tableView.reloadData()
, прокрутка вверх становится прерывистой.
Я считаю, что это связано с тем, что, поскольку я перезагружал данные при прокрутке вверх, UITableView пересчитывает высоту для каждой ячейки, становящейся видимой. Как мне смягчить это или как мне перезагрузить данные только из определенного IndexPath в конец UITableView?
Кроме того, когда мне удается прокрутить до самого верха, я могу прокрутить назад вниз, а затем вверх, никаких проблем с прыжками. Скорее всего, это связано с тем, что высоты UITableViewCell уже были рассчитаны.
источник
reloadRowsAtIndexPaths
. Но (2) что вы подразумеваете под «нервным» и (3) вы установили примерную высоту строки? (Просто пытаюсь выяснить, есть ли лучшее решение, которое позволило бы вам динамически обновлять таблицу.)x
до последней строки в моем TableView ... но поскольку я вставляю новые строки, это не сработает, я не могу знать, каким будет конец моего tableview, прежде чем я перезагружу данные.Ответы:
Чтобы предотвратить скачки, вы должны сохранять высоту ячеек при их загрузке и указывать точное значение в
tableView:estimatedHeightForRowAtIndexPath
:Swift:
var cellHeights = [IndexPath: CGFloat]() func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cellHeights[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeights[indexPath] ?? UITableView.automaticDimension }
Цель C:
// declare cellHeightsDictionary NSMutableDictionary *cellHeightsDictionary = @{}.mutableCopy; // declare table dynamic row height and create correct constraints in cells tableView.rowHeight = UITableViewAutomaticDimension; // save height - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { [cellHeightsDictionary setObject:@(cell.frame.size.height) forKey:indexPath]; } // give exact height value - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { NSNumber *height = [cellHeightsDictionary objectForKey:indexPath]; if (height) return height.doubleValue; return UITableViewAutomaticDimension; }
источник
cellHeightsDictionary
:cellHeightsDictionary = [NSMutableDictionary dictionary];
estimatedHeightForRowAtIndexPath:
возвращает двойное значение, может вызвать*** Assertion failure in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:]
ошибку.return floorf(height.floatValue);
Вместо этого, чтобы исправить это .Swift 3 версия принятого ответа.
var cellHeights: [IndexPath : CGFloat] = [:] func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cellHeights[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeights[indexPath] ?? 70.0 }
источник
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
, она обрабатывает все необходимые мне вычисления высоты.UITableViewDelegate
в свой класс. Соответствие этому протоколу необходимо, поскольку он содержит указанную вышеwillDisplay
функцию. Я надеюсь, что смогу спасти кого-то от такой же борьбы.Прыжок вызван неверной оценкой высоты. Чем больше оценкаRowHeight отличается от фактической высоты, тем больше таблица может подскочить при перезагрузке, особенно чем ниже она была прокручена. Это связано с тем, что предполагаемый размер таблицы радикально отличается от ее фактического размера, что вынуждает таблицу корректировать размер содержимого и смещение. Таким образом, расчетная высота не должна быть случайной величиной, а должна быть близкой к тому, что, по вашему мнению, будет высотой. Я также испытал, когда я установил,
UITableViewAutomaticDimension
если ваши ячейки одного типа, тогдаfunc viewDidLoad() { super.viewDidLoad() tableView.estimatedRowHeight = 100//close to your cell height }
если у вас есть множество ячеек в разных секциях, то я думаю, что лучшее место
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { //return different sizes for different cells if you need to return 100 }
источник
tableView(_:estimatedHeightForHeaderInSection:)
Ответ @Igor в этом случае работает нормально, его
Swift-4
код.// declaration & initialization var cellHeightsDictionary: [IndexPath: CGFloat] = [:]
в следующих методах
UITableViewDelegate
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { // print("Cell height: \(cell.frame.size.height)") self.cellHeightsDictionary[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { if let height = self.cellHeightsDictionary[indexPath] { return height } return UITableView.automaticDimension }
источник
Я пробовал все описанные выше обходные пути, но ничего не помогло.
Проведя часы и пережив все возможные разочарования, нашел способ исправить это. Это решение - спаситель жизни! Работал как шарм!
Swift 4
let lastContentOffset = tableView.contentOffset tableView.beginUpdates() tableView.endUpdates() tableView.layer.removeAllAnimations() tableView.setContentOffset(lastContentOffset, animated: false)
Я добавил его как расширение, чтобы код выглядел чище и не писал все эти строки каждый раз, когда я хочу перезагрузить.
extension UITableView { func reloadWithoutAnimation() { let lastScrollOffset = contentOffset beginUpdates() endUpdates() layer.removeAllAnimations() setContentOffset(lastScrollOffset, animated: false) } }
наконец-то ..
ИЛИ вы можете добавить эту строку в свой
UITableViewCell
awakeFromNib()
методlayer.shouldRasterize = true layer.rasterizationScale = UIScreen.main.scale
и делай нормально
reloadData()
источник
reloadWithoutAnimation
но гдеreload
часть?tableView.reloadData()
сначала позвонить, а затемtableView.reloadWithoutAnimation()
он все еще работает.Я использую больше способов как это исправить:
Для контроллера представления:
var cellHeights: [IndexPath : CGFloat] = [:] func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cellHeights[indexPath] = cell.frame.size.height } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeights[indexPath] ?? 70.0 }
как расширение для UITableView
extension UITableView { func reloadSectionWithouAnimation(section: Int) { UIView.performWithoutAnimation { let offset = self.contentOffset self.reloadSections(IndexSet(integer: section), with: .none) self.contentOffset = offset } } }
Результат
источник
Я столкнулся с этим сегодня и заметил:
cellForRowAtIndexPath
не помогает.Исправить на самом деле было довольно просто:
Переопределите
estimatedHeightForRowAtIndexPath
и убедитесь, что он возвращает правильные значения.С этим все странное дрожание и скачки в моих UITableViews прекратились.
ПРИМЕЧАНИЕ: я действительно знаю размер своих клеток. Есть только два возможных значения. Если ваши ячейки действительно имеют переменный размер, то вы можете захотеть кэшировать
cell.bounds.size.height
изtableView:willDisplayCell:forRowAtIndexPath:
источник
Фактически вы можете перезагрузить только определенные строки, используя
reloadRowsAtIndexPaths
, например:tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)
Но, как правило, вы также можете анимировать изменение высоты ячейки таблицы следующим образом:
источник
Вот немного более короткая версия:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension }
источник
Переопределение метода EstimatedHeightForRowAtIndexPath высоким значением, например 300f
Это должно решить проблему :)
источник
Есть ошибка, которая, как мне кажется, появилась в iOS11.
Это когда вы делаете
reload
tableViewcontentOffSet
неожиданно изменяется. ФактическиcontentOffset
не должно измениться после перезагрузки. Обычно это происходит из-за просчетовUITableViewAutomaticDimension
Вы должны сохранить
contentOffSet
его и вернуть его к сохраненному значению после завершения перезагрузки.func reloadTableOnMain(with offset: CGPoint = CGPoint.zero){ DispatchQueue.main.async { [weak self] () in self?.tableView.reloadData() self?.tableView.layoutIfNeeded() self?.tableView.contentOffset = offset } }
Как вы это используете?
someFunctionThatMakesChangesToYourDatasource() let offset = tableview.contentOffset reloadTableOnMain(with: offset)
Этот ответ был получен отсюда
источник
Этот работал у меня в Swift4:
extension UITableView { func reloadWithoutAnimation() { let lastScrollOffset = contentOffset reloadData() layoutIfNeeded() setContentOffset(lastScrollOffset, animated: false) } }
источник
Ни одно из этих решений не помогло мне. Вот что я сделал с Swift 4 и Xcode 10.1 ...
В viewDidLoad () объявите динамическую высоту строки таблицы и создайте правильные ограничения в ячейках ...
tableView.rowHeight = UITableView.automaticDimension
Также в viewDidLoad () зарегистрируйте все перья ячеек tableView в tableview следующим образом:
tableView.register(UINib(nibName: "YourTableViewCell", bundle: nil), forCellReuseIdentifier: "YourTableViewCell") tableView.register(UINib(nibName: "YourSecondTableViewCell", bundle: nil), forCellReuseIdentifier: "YourSecondTableViewCell") tableView.register(UINib(nibName: "YourThirdTableViewCell", bundle: nil), forCellReuseIdentifier: "YourThirdTableViewCell")
В tableView heightForRowAt возвращает высоту, равную высоте каждой ячейки в indexPath.row ...
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.row == 0 { let cell = Bundle.main.loadNibNamed("YourTableViewCell", owner: self, options: nil)?.first as! YourTableViewCell return cell.layer.frame.height } else if indexPath.row == 1 { let cell = Bundle.main.loadNibNamed("YourSecondTableViewCell", owner: self, options: nil)?.first as! YourSecondTableViewCell return cell.layer.frame.height } else { let cell = Bundle.main.loadNibNamed("YourThirdTableViewCell", owner: self, options: nil)?.first as! YourThirdTableViewCell return cell.layer.frame.height } }
Теперь укажите предполагаемую высоту строки для каждой ячейки в tableView EstimatedHeightForRowAt. Будьте точны, как можете ...
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.row == 0 { return 400 // or whatever YourTableViewCell's height is } else if indexPath.row == 1 { return 231 // or whatever YourSecondTableViewCell's height is } else { return 216 // or whatever YourThirdTableViewCell's height is } }
Это должно сработать ...
Мне не нужно было сохранять и устанавливать contentOffset при вызове tableView.reloadData ()
источник
У меня 2 разной высоты ячейки.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160) return Helper.makeDeviceSpecificCommonSize(cellHeight) }
После того, как я добавил EstimatedHeightForRowAt , прыжков больше не было.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160) return Helper.makeDeviceSpecificCommonSize(cellHeight) }
источник
Один из подходов к решению этой проблемы, который я нашел, -
CATransaction.begin() UIView.setAnimationsEnabled(false) CATransaction.setCompletionBlock { UIView.setAnimationsEnabled(true) } tableView.reloadSections([indexPath.section], with: .none) CATransaction.commit()
источник
Попытайтесь позвонить,
cell.layoutSubviews()
прежде чем возвращать камеруfunc cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?
. Известная ошибка в iOS8.источник
Вы можете использовать следующее в
ViewDidLoad()
tableView.estimatedRowHeight = 0 // if have just tableViewCells <br/> // use this if you have tableview Header/footer <br/> tableView.estimatedSectionFooterHeight = 0 <br/> tableView.estimatedSectionHeaderHeight = 0
источник
У меня было такое поведение прыжков, и я изначально смог смягчить его, установив точную предполагаемую высоту заголовка (потому что у меня был только один возможный вид заголовка), однако затем прыжки начали происходить внутри заголовков, не затрагивая больше всю таблицу.
Следуя приведенным здесь ответам, я понял, что это связано с анимацией, поэтому я обнаружил, что представление таблицы находится внутри представления стека, и иногда мы вызывали
stackView.layoutIfNeeded()
внутри блока анимации. Мое последнее решение заключалось в том, чтобы убедиться, что этот вызов не произойдет, если «действительно» не нужно, потому что макет «при необходимости» имеет визуальное поведение в этом контексте, даже когда «не нужен».источник
Я была такая же проблема. У меня была разбивка на страницы и перезагрузка данных без анимации, но это не помогло прокрутке предотвратить прыжок. У меня разные размеры айфонов, на iphone8 прокрутка не шла, а на iphone7 + -
Я применил следующие изменения к функции viewDidLoad :
self.myTableView.estimatedRowHeight = 0.0 self.myTableView.estimatedSectionFooterHeight = 0 self.myTableView.estimatedSectionHeaderHeight = 0
и моя проблема решена. Надеюсь, тебе это тоже поможет.
источник
На самом деле я обнаружил, если вы используете
reloadRows
вызывающую проблему с прыжком. Тогда вы должны попробовать использоватьreloadSections
вот так:UIView.performWithoutAnimation { tableView.reloadSections(NSIndexSet(index: indexPath.section) as IndexSet, with: .none) }
источник