Swift:在UITableViewCell中加载图像asynchronous

我有一个用代码创build的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 } } 

在其他类中我有一个下载图像的扩展方法asynchronous:

  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") } } } 

我在其他地方使用这个function,并正常工作,基于我的日志,我明白,下载的图像没有问题(当单元格呈现时)和图像下载后,细胞的用户界面没有更新。

此外,我试图使用像哈内克caching库,但问题是存在的,而不是改变。

请帮我理解错误

谢谢

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

编辑:从setNeedsLayout更正为layoutSubviews

问题是, UITableViewCell.Subtitle呈现将尽快cellForRowAtIndexPath返回(重写您尝试设置图像视图的frame )布局单元格。 因此,如果你asynchronous检索图像,那么这个单元格将被重新布置,就好像没有图像显示(因为你没有将图像视图的image属性初始化为任何东西),并且当你稍后asynchronous更新imageView ,那么这个单元格就已经被放置了,所以你将无法看到你下载的图像。

这里有几个解决scheme:

  1. 您可以让downloadFrom将图像更新为default不仅在没有URL的情况下,而且在存在URL的情况下(因此您将首先将其设置为默认图像,稍后将图像更新为从下载的图像networking):

     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") } } } 

    这确保了单元格将被布置为存在图像,而不pipe怎样,因此图像视图的asynchronous更新将起作用(类似于:参见下文)。

  2. 而不是使用UITableViewCell的dynamic布局的.Subtitle格式,您也可以创build自己的单元格原型,这个原型格式适合于图像视图的固定大小。 这样,如果没有立即可用的图像,则不会将该单元格重新格式化,就好像没有图像可用。 这使您可以使用自动布局完全控制单元格的格式。

  3. 你也可以定义你的downloadFrom方法来获取额外的第三个参数,这个闭包在下载完成后会被调用。 然后你可以在那个闭包里面做一个reloadRowsAtIndexPaths 。 不过,这是假设你修复这个代码来caching下载的图片(例如在一个NSCache中),这样你就可以在再次下载之前检查是否有caching的图片。

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

  1. 如果向下滚动然后向上滚动,则将从networking中重新获取图像。 你真的想caching之前下载的图像,然后再次检索它们。

    理想情况下,您的服务器的响应标头configuration正确,以便内置的NSURLCache将为您照顾,但您必须testing。 或者,您可以将图像自己caching在自己的NSCache

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

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

就个人而言,我可能会build议您(a)使用dequeueCellForRowAtIndexPath ,然后(b)将其与诸如AlamofireImage , SDWebImage , DFImageManager或Kingfisher等已build立的UIImageViewCell类别之一结合使用。 做必要的caching和取消先前的请求是一个不平凡的练习,使用其中一个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 } 

因此,您不仅可以享受基本的asynchronous图像更新,还可以享受图像caching,可视图像的优先级sorting,因为我们正在重复使用已出列的单元格,效率更高等。通过使用带约束的单元格原型和您的自定义表格视图单元格子类,所有东西都布置得正确,避免了手动调整代码中的frame

无论您使用哪种UIImageView扩展,这个过程基本上都是相同的,但目标是让您摆脱自己编写扩展的杂草。

通过inheritanceUITableViewCell来创build自己的单元格。 即使属性可用,您正在使用的样式.Subtitle也没有图像视图。 只有样式UITableViewCellStyleDefault有一个图像视图。

哦,我的上帝,layoutSubviews不build议直接使用
解决问题的正确方法是调用:
[self setNeedsLayout];
[self layoutIfNeeded];
在这里,双方必须一起打电话。
试试这个,祝你好运。

首选SDWebImages库是链接

它将下载图像asynchronous并caching图像也很容易融入到项目中