UITableView прыгает и мерцает при прокрутке
У меня есть UITableView
который отображает ячейки с изображением и текстом. Данные запрашиваются по запросу - сначала я запрашиваю данные для 10 строк, затем для следующих 10 и так далее. Я делаю это в tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
, Проблема в том, что когда я получаю данные и мне нужно обновить tableview
иногда он прыгает и / или мерцает. Я звоню reloadData
, Вот часть кода:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
DispatchQueue.global(qos: .background).async {
if indexPath.row + 5 >= self.brands.count && !BrandsManager.pendingBrandsRequest {
BrandsManager.getBrands() { (error, brands) in
self.brands.append(contentsOf: brands as! [Brand])
DispatchQueue.main.async {
UIView.performWithoutAnimation {
self.brandsTableView.reloadData()
}
}
}
}
}
}
Высота ячеек постоянна возвращается следующим образом:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
Я использую Kingfisher для загрузки и кэширования изображений. Вот еще немного кода из источника данных:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return brands.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.ImageTableCell, for: indexPath) as! ImageTableViewCell
let brand = brands[indexPath.row]
cell.centerLabel.text = brand.brand
cell.leftImageView.image = nil
if let url = BrandsManager.brandLogoURL(forLogoName: brand.logo!) {
let resource = ImageResource(downloadURL: url, cacheKey: url.absoluteString)
cell.leftImageView.kf.setImage(with: resource)
} else {
print("Cannot form url for brand logo")
}
return cell
}
Как я могу избежать мерцания и скачка табличного представления на свитке? Я посмотрел на некоторые из подобных вопросов, но не смог найти рабочее решение для моего случая.
2 ответа
Чтобы убрать проблему с прыжками, вам нужно установить estimatedHeightForRowAt
такой же, как ваша высота строки. Предполагая, что у вас не будет проблем с производительностью, вы можете просто сделать следующее:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.tableView(tableView, heightForRowAt: indexPath)
}
Или, если высота ячейки постоянна, вы можете сделать tableView.estimatedRowHeight = 70.0
,
Почему это происходит, потому что табличное представление при перезагрузке будет использовать estimatedRowHeight
для ячеек, которые невидимы, что приводит к прыжкам, когда расчетная высота отличается от фактической. Чтобы дать вам идею:
Допустим, что предполагаемая высота 50
в то время как реальная высота 75
, Теперь, когда вы прокрутили вниз так, что 10 ячеек за пределами экрана у вас есть 10*75 = 750
пиксели смещения контента. Нет, когда происходит перезагрузка, табличное представление будет игнорировать, сколько ячеек скрыто, и попытается пересчитать это. Он будет повторно использовать приблизительную высоту строки, пока не найдет путь индекса, который должен быть видимым. В этом примере он начинает называть ваш estimatedHeightForRow
с указателями [0, 1, 2...
и увеличение offset
от 50
пока он не доберется до вашего смещения контента, который по-прежнему 750
, Так что это означает, что он попадает в индекс 750/50 = 15
, И это производит прыжок из клетки 10
в камеру 15
на перезагрузке.
Что касается мерцания, есть много возможностей. Вы можете избежать перезагрузки ячеек, которые не нуждаются в перезагрузке, перезагрузив только часть источника данных, которая изменилась. В вашем случае это означает вставку новых строк, таких как:
tableView.beginUpdates()
tableView.insertRows(at: myPaths, with: .none)
tableView.endUpdates()
Тем не менее, кажется странным, что вы даже видите мерцание. Если только изображение мерцает, то проблема может быть в другом месте. Получение такого изображения обычно является асинхронной операцией, даже если изображение уже кэшировано. Вы можете избежать этого, проверив, действительно ли вам нужно обновить ресурс. Если ваша ячейка уже отображает изображение, которое вы пытаетесь показать, то нет смысла применять новый ресурс:
if let url = BrandsManager.brandLogoURL(forLogoName: brand.logo!) {
if url != cell.currentLeftImageURL { // Check if new image needs to be applied
let resource = ImageResource(downloadURL: url, cacheKey: url.absoluteString)
cell.currentLeftImageURL = url // Save the new URL
cell.leftImageView.kf.setImage(with: resource)
}
} else {
print("Cannot form url for brand logo")
}
Я бы предпочел поместить этот код в саму ячейку, хотя
var leftImageURL: URL {
didSet {
if(oldValue != leftImageURL) {
let resource = ImageResource(downloadURL: url, cacheKey: url.absoluteString)
leftImageView.kf.setImage(with: resource)
}
}
}
но это полностью зависит от вас.
Если вы добавляете данные в конец tableView, не вызывайте reloadData, что вызывает пересчет и перерисовку всех ячеек. Вместо этого используйте UITableView.insertRows(at:with:), который будет выполнять соответствующую анимацию вставки, если вы используете.automatic и оставляете существующие ячейки в покое.