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