繁体   English   中英

Swift:在 UITableViewCell 中异步加载图像

[英]Swift: load images Async in UITableViewCell

我有一个用代码创建的tableview (没有storyboard ):

class MSContentVerticalList: MSContent,UITableViewDelegate,UITableViewDataSource {
var tblView:UITableView!
var dataSource:[MSC_VCItem]=[]

init(Frame: CGRect,DataSource:[MSC_VCItem]) {
    super.init(frame: Frame)
    self.dataSource = DataSource
    tblView = UITableView(frame: Frame, style: .Plain)
    tblView.delegate = self
    tblView.dataSource = self
    self.addSubview(tblView)
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return dataSource.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: nil)

    let record = dataSource[indexPath.row]
    cell.textLabel!.text = record.Title
    cell.imageView!.downloadFrom(link: record.Icon, contentMode: UIViewContentMode.ScaleAspectFit)
    cell.imageView!.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    print(cell.imageView!.frame)
    cell.detailTextLabel!.text = record.SubTitle
    return cell
}
}

在其他课程中,我有一个用于异步下载图像的扩展方法:

   extension UIImageView
{
    func downloadFrom(link link:String?, contentMode mode: UIViewContentMode)
    {
        contentMode = mode
        if link == nil
        {
            self.image = UIImage(named: "default")
            return
        }
        if let url = NSURL(string: link!)
        {
            print("\nstart download: \(url.lastPathComponent!)")
            NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, _, error) -> Void in
                guard let data = data where error == nil else {
                    print("\nerror on download \(error)")
                    return
                }
                dispatch_async(dispatch_get_main_queue()) { () -> Void in
                    print("\ndownload completed \(url.lastPathComponent!)")
                    self.image = UIImage(data: data)
                }
            }).resume()
        }
        else
        {
            self.image = UIImage(named: "default")
        }
    }
}

我在其他地方使用了这个功能并且工作正常,根据我的日志,我了解到下载的图像没有问题(渲染单元格时)并且在下载图像后,单元格 UI 没有更新。

我也尝试使用像Haneke这样的缓存库,但问题存在并且没有改变。

请帮助我理解错误

谢谢

设置图像后,您应该调用self.layoutSubviews()

编辑:从setNeedsLayout更正为layoutSubviews

问题是UITableViewCell.Subtitle再现将在cellForRowAtIndexPath返回后立即布局单元格(覆盖您设置图像视图frame的尝试)。 因此,如果你是异步检索图像,细胞将被重新布置,仿佛没有图像显示(因为你没有初始化摄像画面的image属性来任何东西),当你更新imageView异步后,单元格的布局方式已经使您无法看到您下载的图像。

这里有几个解决方案:

  1. 您可以让downloadFrom将图像更新为default不仅在没有 URL 时,而且在有 URL 时(因此您首先将其设置为默认图像,然后将图像更新为您下载的图像)网络):

     extension UIImageView { func downloadFrom(link link:String?, contentMode mode: UIViewContentMode) { contentMode = mode image = UIImage(named: "default") if link != nil, let url = NSURL(string: link!) { NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in guard let data = data where error == nil else { print("\\nerror on download \\(error)") return } if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode != 200 { print("statusCode != 200; \\(httpResponse.statusCode)") return } dispatch_async(dispatch_get_main_queue()) { print("\\ndownload completed \\(url.lastPathComponent!)") self.image = UIImage(data: data) } }.resume() } else { self.image = UIImage(named: "default") } } }

    这确保了单元格将被布置为存在图像,无论如何,因此图像视图的异步更新将起作用(有点:见下文)。

  2. 除了使用UITableViewCell的动态布局.Subtitle呈现,您还可以创建自己的单元格原型,该原型以固定大小的图像视图适当布局。 这样,如果没有立即可用的图像,它不会像没有可用的图像一样重新格式化单元格。 这使您可以使用自动布局完全控制单元格的格式。

  3. 您还可以定义您的downloadFrom方法以获取额外的第三个参数,即下载完成后您将调用的闭包。 然后您可以在该闭包内执行reloadRowsAtIndexPaths 但是,这假设您修复了此代码以缓存下载的图像(例如在NSCache中),以便您可以在再次下载之前检查是否有缓存的图像。

话虽如此,正如我上面提到的,这种基本模式存在一些问题:

  1. 如果向下滚动然后向上滚动,您将重新从网络中检索图像。 您确实希望在再次检索之前缓存先前下载的图像。

    理想情况下,您的服务器的响应标头配置正确,以便内置的NSURLCache会为您处理此问题,但您必须对其进行测试。 或者,您可以自己将图像缓存在自己的NSCache

  2. 如果您快速向下滚动到第 100 行,您真的不希望可见单元格积压在前 99 行不再可见的图像请求后面。 您确实想取消对滚动到屏幕外的单元格的请求。 (或者使用dequeueCellForRowAtIndexPath ,在那里你重用单元格,然后你可以编写代码来取消之前的请求。)

  3. 如上所述,您确实希望执行dequeueCellForRowAtIndexPath以便您不必不必要地实例化UITableViewCell对象。 你应该重用它们。

就个人而言,我可能会建议您 (a) 使用dequeueCellForRowAtIndexPath ,然后 (b) 将其与完善的UIImageViewCell类别之一结合,例如AlamofireImageSDWebImageDFImageManagerKingfisher 对先前的请求进行必要的缓存和取消是一项重要的工作,使用这些UIImageView扩展之一将简化您的生活。 如果您决定自己做这件事,您可能仍然希望查看这些扩展的一些代码,以便您可以获取有关如何正确执行此操作的想法。

——

例如,使用AlamofireImage ,您可以:

  1. 定义自定义表格视图单元格子类:

     class CustomCell : UITableViewCell { @IBOutlet weak var customImageView: UIImageView! @IBOutlet weak var customTitleLabel: UILabel! @IBOutlet weak var customSubtitleLabel: UILabel! }
  2. 将单元原型添加到您的表视图故事板,指定 (a) CustomCell的基类; (b) CustomCell的故事板 ID; (c) 将图像视图和两个标签添加到您的单元格原型,将@IBOutlets到您的CustomCell子类; (d) 添加定义图像视图和两个标签的位置/大小所需的任何约束。

    您可以使用自动布局约束来定义图像视图的尺寸

  3. 您的cellForRowAtIndexPath可以执行以下操作:

     override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell let record = dataSource[indexPath.row] cell.customTitleLabel.text = record.Title cell.customSubtitleLabel.text = record.SubTitle if let urlString = record.Icon { cell.customImageView.af_setImageWithURL(NSURL(string: urlString)!) } return cell }

有了它,您不仅可以享受基本的异步图像更新,还可以享受图像缓存、可见图像的优先级排序,因为我们正在重用出列单元格,这样效率更高等等。并且通过使用带有约束的单元格原型和您的自定义表格视图单元格子类,一切都正确布局,使您frame在代码中手动调整frame

无论您使用这些UIImageView扩展中的哪一个,该过程在很大程度上都是相同的,但目标是让您摆脱自己编写扩展的麻烦。

我的天,layoutSubviews不建议直接使用
解决问题的正确方法是调用:
[self setNeedsLayout];
[self layoutIfNeeded];
在这里,两种方式不得不一起调用。
试试这个,祝你好运。

通过继承 UITableViewCell 创建你自己的单元格。 您正在使用的样式 .Subtitle 没有图像视图,即使该属性可用。 只有 UITableViewCellStyleDefault 样式具有图像视图。

更喜欢这里的 SDWebImages 库是链接

它将下载图像异步并缓存图像,并且也很容易集成到项目中

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM