【问题标题】:Table cell frame not updated after Image downloaded asynchronously异步下载图像后表格单元格框架未更新
【发布时间】:2019-04-18 16:41:50
【问题描述】:

我有一个带有 UITableViewAutomaticDimension 的 UITableViewCell,它由图像和标题组成。从 API 获取数据后,我重新加载 tableView,但只显示 UILabel 中的标题而不显示图像,如下图所示

滚动 tableView 后,图像会出现并根据那里的大小进行调整。像下面

我希望这发生在滚动之前。

我已经尝试过的事情

  1. 使用占位符图像,但会导致下载的图像采用占位符图像大小,并且滚动后单元格大小会正确更新。
  2. 根据需要使用布局或 setNeedLayout
  3. 等待文件下载,然后在计算大小后返回单元格,但这会导致滚动时滞后于表格视图

我的 cellForRowAt 代码

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.custom.rawValue) as? CustomCell else {return UITableViewCell()}
    cell.update(model: arrImages[indexPath.row])
    return cell
}

我的单元类逻辑

更新方法

func update(model: ImageModel) {
    self.presentModel = model
}

单元格中的 UI 更改

    var presentModel: ImageModel? {
    didSet{
        if let url = presentModel?.imgURL {
            imgView.kf.indicatorType = .activity
            imgView.kf.setImage(with: url)
        }
        lblTitle.text = presentModel?.title
    }
}

补充信息 我正在使用 KingFisher 第三方下载图片。

【问题讨论】:

  • 从 url 加载图像时,您应该重新加载表格视图。
  • 我有多个单元格,每个单元格都有自己的图像。
  • 这似乎是你的问题:github.com/onevcat/Kingfisher/issues/839
  • @user832 请看我的回答。希望对您的情况有所帮助。
  • @Socram 我检查了一下,但 cell.setsNeedLayout 对我不起作用。

标签: ios uitableview swift4


【解决方案1】:

从网址加载图像后,您应该重新加载表格视图。

您可能有多个单元格,每个单元格上都有图像。因此,从 url 异步下载图像后,您应该调用 tableView 的 reload 方法。

您说当您在屏幕上向下滚动单元格时图像会正确显示,因为当单元格即将在屏幕上可见时将调用 tableView 的 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)。

所以你应该调用tableView.reloadData()tableView.reloadRows(at: [imageLoadedCellIndexPath], with: .automatic)方法来触发tableview的委托和数据源方法。

由于您正在使用Kingfisher扩展从url下载图片,您可以尝试使用下面的方法来了解下载状态。

imageView.kf.setImage(with: url, completionHandler: { 
    (image, error, cacheType, imageUrl) in

})

而不是imageView.kf.setImage(with: url).

例子:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.custom.rawValue) as? CustomCell else {return UITableViewCell()}

    cell.update(model: arrImages[indexPath.row])

    let imageModel = arrImages[indexPath.row]

    if let url = imageModel?.imgURL {

//Check if image is already downloaded
        ImageCache.default.retrieveImage(forKey: url.absoluteString, options: nil) {
            image, cacheType in

//Image is already downloaded
            if let image = image {

                //cell.imgView.image = image
                imgView.kf.setImage(with: url) //try this line instead of above one

            } else { //Download
                imgView.kf.indicatorType = .activity
                cell.imgView.kf.setImage(with: url, completionHandler: {
                    (image, error, cacheType, imageUrl) in

                    if image != nil{

                        tableView.reloadRows(at: [indexPath], with: .automatic)

                        //OR

                        tableView.reloadData()
                    }
                })
            }
        }

    }

    return cell
}

并在单元格中注释图像下载代码,如下所示,

var presentModel: ImageModel? {
    didSet{
//            if let url = presentModel?.imgURL {
//                imgView.kf.indicatorType = .activity
//                imgView.kf.setImage(with: url)
//            }
              lblTitle.text = presentModel?.title
        }
    }

【讨论】:

  • 您的解决方案导致视图失真,现在图像与单元格重叠,请查看此图像链接:[ibb.co/iXxVFL]
  • 问题可能出在“cell.imgView.image = image”。请查看我的更新答案。
  • 更新后的代码导致滚动时出现卡顿,几乎挂掉的情况,有时还会崩溃。
【解决方案2】:

关注

  • 在表格单元格中添加堆栈视图
  • 向堆栈视图添加添加高度约束
  • 创建此高度约束的IBOutlet
  • 使用带有完成块的 Kingfisher SDK 加载带有 URL 的图像。
  • 在完成块内部计算您要设置到图像视图的高度
  • 使用计算出的高度和宽度创建带有框架的UIImageView
  • 在堆栈视图中添加 imageViewarrangedsubView
  • 将约束的常量值设置为您计算的高度
  • 在将imageView 添加到堆栈视图后添加这两行
    stackView.setNeedsLayout()
    stackView.layoutIfNeeded()
    

示例代码

let imageSize = image.size
let width = UIScreen.main.bounds.width - 20
let height = (width * imageSize.height) /  imageSize.width
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageView.image = image
heightConstraint.constant = height
stackView.addArrangedSubview(imageView)
stackView.setNeedsLayout()
stackView.layoutIfNeeded()

【讨论】: