我正在尝试根据下载的图像的大小来实现动态调整的行高。我遇到的问题是运行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)
}

最佳答案

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

因此,在检索图像之前,将为每个可见单元调用一次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)
            }
        }
    }
}

10-08 15:44