这是使用heightForRowAt的正确方法吗?

时间:2018-01-14 17:43:28

标签: ios swift uitableview

我试图根据下载图像的大小实现动态大小的行高。我遇到的问题是在运行函数heightForRowAt时不会下载图像。实现此代码的正确方法是什么。 images是UIImage的数组,rowHeights是CGFloat类型的数组,imageURLS是imageURLS的字符串数组。

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Reuse", for: indexPath) as! TableViewCell

    // Configure the cell...

    ///////////////////////
 if(cell.cellImageView.image == nil){   
        let downloadURL = URL(string: self.imageURLS[indexPath.row])

      URLSession.shared.dataTask(with: downloadURL!) { (data, _, _) in
        if let data = data {
            let image = UIImage(data: data)
            DispatchQueue.main.async {
                cell.cellImageView.image = image
                cell.cellImageView.contentMode = .scaleAspectFit
                self.images.insert(image!, at: 0)
                let aspectRatio = Float((cell.cellImageView?.image?.size.width)!/(cell.cellImageView?.image?.size.height)!)
                print("aspectRatio: \(aspectRatio)")
                tableView.rowHeight = CGFloat(Float(UIScreen.main.bounds.width)/aspectRatio)
              print("tableView.rowHeight: \(tableView.rowHeight)")
                self.rowHeights.insert(CGFloat(Float(UIScreen.main.bounds.width)/aspectRatio), at: 0)
             tableView.reloadRows(at: [indexPath], with: .top)
            }
        }
        }.resume()
 }


    ///////////////////////
    return cell
}

//What is the proper way to implement this function
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    print("Im in height for row")
    return CGFloat(0.0)
}

1 个答案:

答案 0 :(得分:1)

如果您的异步请求可能会更改单元格的高度,则不应直接更新单元格,而是应该完全重新加载单元格。

因此,在检索图像之前,将为每个可见单元格调用heightForRowAtcellForRowAt一次。由于尚未检索到图像,heightForRowAt必须返回适合没有图像的单元格的固定值。并且cellForRowAt应该检测到图像尚未被检索并启动该过程。但是,当完成图像检索时,cellForRowAt应该调用reloadRows(at:with:)而不是直接更新单元格。这将为此行再次启动该过程,包括再次触发heightForRowAt。但是这一次,图片应该在那里,因此heightForRowAt现在可以返回适当的高度,cellForRowAt现在只需更新图片视图而无需进一步的网络请求。

例如:

class ViewController: UITableViewController {

    private var objects: [CustomObject]!

    override func viewDidLoad() {
        super.viewDidLoad()

        objects = [
            CustomObject(imageURL: URL(string: "https://upload.wikimedia.org/wikipedia/commons/e/e8/Second_Life_Landscape_01.jpg")!),
            CustomObject(imageURL: URL(string: "https://upload.wikimedia.org/wikipedia/commons/7/78/Brorfelde_landscape_2.jpg")!)
        ]
    }

    let imageCache = ImageCache()

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
        let imageURL = objects[indexPath.row].imageURL
        if let image = imageCache[imageURL] {
            // if we got here, we found image in our cache, so we can just 
            // update image view and we're done

            cell.customImageView.image = image
        } else {
            // if we got here, we have not yet downloaded the image, so let's
            // request the image and then reload the cell

            cell.customImageView.image = nil  // make sure to reset the image view

            URLSession.shared.dataTask(with: imageURL) { data, _, error in
                guard let data = data, error == nil else {
                    print(error ?? "Unknown error")
                    return
                }
                if let image = UIImage(data: data) {
                    self.imageCache[imageURL] = image
                    DispatchQueue.main.async {
                        // NB: This assumes that rows cannot be inserted while this asynchronous
                        // request is underway. If that is not a valid assumption, you will need to
                        // go back to your model and determine what `IndexPath` now represents
                        // this row in the table.

                        tableView.reloadRows(at: [indexPath], with: .middle)
                    }
                }
            }.resume()
        }
        return cell
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return objects.count
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let imageURL = objects[indexPath.row].imageURL
        if let image = imageCache[imageURL] {
            let size = image.size
            return view.bounds.size.width * size.height / size.width
        } else {
            return 0
        }
    }
}

简单图像缓存(与您的问题无关,但为了完整性而包含)如下:

class ImageCache {
    private let cache = NSCache<NSURL, UIImage>()

    private var observer: NSObjectProtocol!

    init () {
        observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] _ in
            self?.cache.removeAllObjects()
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(observer)
    }

    subscript(key: URL) -> UIImage? {
        get {
            return cache.object(forKey: key as NSURL)
        }
        set (newValue) {
            if let image = newValue {
                cache.setObject(image, forKey: key as NSURL)
            } else {
                cache.removeObject(forKey: key as NSURL)
            }
        }
    }
}