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

Ios 这是使用heightForRowAt的正确方法吗?,ios,swift,uitableview,Ios,Swift,Uitableview,我正在尝试根据下载图像的大小动态调整行高。我遇到的问题是,在运行函数heightForRowAt时,图像没有下载。实现此代码的正确方法是什么。images是UIImage的数组,rowHeights是CGFloat类型的数组,imageURLS是imageURLS的字符串数组 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

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

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

因此,在检索图像之前,将为每个可见单元格调用一次
heightForRowAt
cellForRowAt
。由于尚未检索图像,
heightForRowAt
将必须返回一些适用于没有图像的单元格的固定值。和
cellForRowAt
应检测到图像尚未检索并启动该过程。但是当图像检索完成时,而不是直接更新单元格,
cellForRowAt
应该调用。这将再次启动此行的进程,包括触发再次调用
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)
            }
        }
    }
}
类图像缓存{
private let cache=NSCache()
私有var观察员:NSObjectProtocol!
init(){
observer=NotificationCenter.default.addObserver(forName:.UIApplicationIDReceiveMemoryWarning,对象:nil,队列:nil){[weak self]\uuIn
self?.cache.removeAllObjects()
}
}
脱硝{
NotificationCenter.default.removeObserver(观察者)
}
下标(键:URL)->UIImage{
得到{
返回cache.object(forKey:key作为NSURL)
}
设置(新值){
如果let image=newValue{
setObject(图像,forKey:key作为NSURL)
}否则{
cache.removeObject(forKey:key作为NSURL)
}
}
}
}

就行高而言,最好的方法是设置
estimatedRowHeight
并将
rowHeight
设置为
UITableViewAutomaticDimension
,然后定义自动调整行大小的约束,而根本不实施
heightForRowAt
。请参阅
heightForRowAt
文档中有关实施该方法的缺点的评论:@Rob谢谢您的帮助,我正在尝试调整行高,以便图像保持与其纵横比成比例,而其宽度与设备大小成比例。当我使用
UITableViewAutomaticDimension
时,行高和图像仍然太小。您最近已经问了这个问题()并且接受了答案。如果你接受了上一个问题的答案,为什么要再次发布,就好像你没有接受同一问题的解决方案一样?@user372382-很公平。对于
UITableViewAutomaticDimension
(更新
cellForRowAt
中的约束),总是会出现这种情况,但这有点复杂,所以让我们把它放在一边。不过,我的第一句话仍然适用。只需在完成处理程序中重新加载行,确保(a)下一次调用
cellForRowAt
将使用先前检索到的图像;(b)确保
heightForRowAt
现在将返回正确的高度。@Rob我已按照您的建议更新了代码,以检查图像是否已下载,然后在完成处理程序中重新加载行。但是我无法访问
heightForRowAt
函数中的图像大小。似乎
heightForRowAt
函数在
cellForrowAt
函数之前运行。感谢您的详细回答,如果else语句中的图片尚未下载,代码中的imageView值正在设置。在检索图像后,它不会设置图像视图,而是调用
reloadRows(at:with:)
,这会触发该行再次调用
cellForRowAt
。第二次调用
cellForRowAt
更新图像视图。但我们将其延迟,这样我们就可以享受新的行高度和指定的动画。