【问题标题】:UITableView jumps and flickers when scrollingUITableView 滚动时跳动闪烁
【发布时间】:2018-11-28 19:14:10
【问题描述】:

我有一个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
}

如何避免滚动时表格视图的闪烁和跳跃?我查看了一些类似的问题,但找不到适合我的案例的有效解决方案。

【问题讨论】:

  • 不止几件事可以做到这一点。其中之一是使用估计行高不匹配的自动尺寸。您可能需要创建高度缓存以避免它。但总的来说,请尝试添加更多关于您正在做什么的信息
  • @MaticOblak 感谢您的回复。细胞的高度是恒定的。我更新了我的问题。
  • 如果您使用的是故事板/界面构建器,请关闭单元格的自动尺寸。
  • 仍然只是尝试添加estimatedRowHeight方法并返回70。或者,我相信您可以将其设置为表格视图上的属性。问题是,当您向下滚动并调用重新加载数据时,系统仍会尝试优化并调用估计高度而不是真实高度。默认情况下,我相信估计的高度是 44,所以表格视图可能会跳跃。 (这可能是其他事情,但如果您首先确保不是这种情况,我真的更喜欢)。
  • @MaticOblak 感谢您的建议和解释。提供estimatedHeightForRowheightForRow 相同时,跳跃消失了。闪烁仍然存在,并由 Josh Homann 建议使用.automatic 动画而不是reloadData 调用`UITableView.insertRows(at:with:)` 修复。如果您将您的评论作为答案,我会投赞成票,但我会将整个解决方案作为单独的答案提供并接受它以明确起见。

标签: ios swift uitableview flicker


【解决方案1】:

要消除跳跃问题,您需要将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 像素的内容偏移量。不,当重新加载发生时,表格视图将忽略隐藏了多少单元格并尝试重新计算。它将继续重用估计的行高,直到找到应该可见的索引路径。在这个例子中,它开始使用索引[0, 1, 2... 调用你的estimatedHeightForRow,并将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)
        }
    }
}

但这完全取决于你。

【讨论】:

  • 感谢您的详细解释。如果不需要图像未设置正确,即使使用 reloadData 重新加载,闪烁也会消失。很好的答案。
  • 这给出错误“无法调用非函数类型 uitableview”
  • func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return self.tableView(tableView, heightForRowAt: indexPath) }
  • @HarshThakur 您很可能在“self”上缺少UITableViewDelegate 的实现。另一种选择是您使用的是旧版本的 Swift。
【解决方案2】:

如果您将数据附加到 tableView 的末尾,请不要调用 reloadData,这会强制重新计算和重绘所有单元格。而是使用 UITableView.insertRows(at:with:) 如果您使用 .automatic 并保留现有单元格,它将执行适当的插入动画。

【讨论】:

  • 非常感谢您的建议。它有助于消除闪烁。 Matic Oblac 建议返回与heightForRow 相同的estimatedHeightForRow,从而消除了跳跃。我会将这两个建议合并为一个单独的答案。
猜你喜欢
  • 2018-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多