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
更新图像视图。但我们将其延迟,这样我们就可以享受新的行高度和指定的动画。