UITableView 滚动时跳转和闪烁

sur*_*heW 2 flicker uitableview ios swift

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

                }

            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

单元格的高度是恒定返回的,如下所示:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 70
}
Run Code Online (Sandbox Code Playgroud)

我正在使用 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
}
Run Code Online (Sandbox Code Playgroud)

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

Mat*_*lak 9

要消除跳跃问题,您需要设置estimatedHeightForRowAt与行高相同的值。假设您没有性能问题,您可以简单地执行以下操作:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return self.tableView(tableView, heightForRowAt: indexPath)
}
Run Code Online (Sandbox Code Playgroud)

或者,如果单元格高度不变,您可以执行tableView.estimatedRowHeight = 70.0.

为什么会发生这种情况是因为重新加载时表格视图将estimatedRowHeight用于不可见的单元格,这会导致在估计高度与实际高度不同时跳跃。给你一个想法:

假设估计高度为 ,50而实际高度为75。现在您已经向下滚动,使 10 个单元格离开屏幕,您有10*75 = 750内容偏移的像素。不,当重新加载发生时,表视图将忽略隐藏的单元格数量并尝试重新计算。它将继续重复使用估计的行高,直到找到应该可见的索引路径。在这个例子中,它开始调用你的estimatedHeightForRowwith 索引[0, 1, 2...并增加offsetby50直到它到达你的内容偏移量,它仍然是750. 所以这意味着它到达 index 750/50 = 15。这会在重新加载时从一个单元10格跳到另一个单元格15

至于闪烁,有很多种可能。您可以通过仅重新加载已更改的数据源部分来避免重新加载不需要重新加载的单元格。在您的情况下,这意味着插入新行,例如:

tableView.beginUpdates()
tableView.insertRows(at: myPaths, with: .none)
tableView.endUpdates()
Run Code Online (Sandbox Code Playgroud)

你甚至看到闪烁仍然很奇怪。如果只有图像闪烁,那么问题可能出在其他地方。获取这样的图像通常是一个异步操作,即使图像已经被缓存。您可以通过检查是否真的需要更新资源来避免它。如果您的单元格已经显示您尝试显示的图像,则没有理由应用新资源:

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")
}
Run Code Online (Sandbox Code Playgroud)

我宁愿将此代码放入单元格本身

var leftImageURL: URL {
    didSet {
        if(oldValue != leftImageURL) {
            let resource = ImageResource(downloadURL: url, cacheKey: url.absoluteString)
            leftImageView.kf.setImage(with: resource)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但这完全取决于您。