Swift:在UITableViewCell

时间:2015-11-03 07:29:04

标签: ios swift uitableview haneke

我使用代码创建了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
}
}

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

   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这样的缓存库,但问题是存在而不是更改。

请帮我理解错误

由于

5 个答案:

答案 0 :(得分:10)

设置图片后,您应拨打self.layoutSubviews()

修改:已从setNeedsLayout更正为layoutSubviews

答案 1 :(得分:8)

问题是.Subtitle的{​​{1}}版本会在UITableViewCell返回后立即布局单元格(覆盖您尝试设置图片视图的cellForRowAtIndexPath) 。因此,如果您异步检索图像,则将重新布置单元格,就像没有要显示的图像一样(因为您没有将图像视图的frame属性初始化为任何内容),当您稍后异步更新image时,单元格的排列方式将使您无法看到下载的图像。

这里有几个解决方案:

  1. 您不仅可以在没有网址的情况下imageView将图片更新为downloadFrom,还可以在有网址的情况下将图像更新为default(因此您首先将其设置为默认图像,稍后将图像更新为您从网络下载的图像):

    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. 您可以创建自己的单元格原型,而不是使用.Subtitle的动态布局UITableViewCell再现,而是为图像视图设置适当的固定大小。这样,如果没有立即可用的图像,它就不会重新格式化单元格,就好像没有可用的图像一样。这使您可以使用autolayout完全控制单元格的格式。

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

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

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

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

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

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

    4. 就我个人而言,我可能会建议您(a)使用dequeueCellForRowAtIndexPath,然后(b)将其与一个完善的UIImageViewCell类别结合使用,例如AlamofireImage,{{3 }},SDWebImageDFImageManager。对先前请求进行必要的缓存和取消是一项非常重要的练习,使用其中一个UIImageView扩展将简化您的生活。如果您决定自己这样做,您可能还想查看这些扩展的一些代码,以便您可以了解如何正确执行此操作。

      -

      例如,使用Kingfisher,您可以:

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

        class CustomCell : UITableViewCell {
            @IBOutlet weak var customImageView: UIImageView!
            @IBOutlet weak var customTitleLabel: UILabel!
            @IBOutlet weak var customSubtitleLabel: UILabel!
        }
        
      2. 将单元格原型添加到表视图故事板中,指定(a)基类CustomCell; (b)故事板ID CustomCell; (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
        }
        
      4. 通过这种方式,您不仅可以享受基本的异步图像更新,还可以享受图像缓存,可见图像的优先级排序,因为我们可以重新使用出列的单元格,效率更高等等。并且通过使用单元格原型使用约束和您的自定义表格视图单元格子类,一切都正确布局,使您无需手动调整代码中的frame

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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

首选SDWebImages库是link

它将下载图像异步并缓存图像 并且很容易整合到项目中