Ios 在tableView中下载图像(:,cellForRowAt:)
当我使用tableView切换到viewController时,tableviewcell立即使用方法Ios 在tableView中下载图像(:,cellForRowAt:),ios,swift,uitableview,Ios,Swift,Uitableview,当我使用tableView切换到viewController时,tableviewcell立即使用方法fetchUserAvatar(avatarName:handler:(String)->Void)向服务器发送请求。这个方法返回一个链接到图像的url。下载并缓存图像cacheImage是NSCache的对象。此对象cacheImage是在以前的视图控制器中初始化的,并使用prepare(对于segue:UIStoryboardSegue,sender:Any?)从pervious分配给此Vi
fetchUserAvatar(avatarName:handler:(String)->Void)
向服务器发送请求。这个方法返回一个链接到图像的url。下载并缓存图像cacheImage
是NSCache
的对象。此对象cacheImage
是在以前的视图控制器中初始化的,并使用prepare(对于segue:UIStoryboardSegue,sender:Any?)从pervious分配给此ViewController
。当这个viewController出现时,我在单元格中看不到图像。但是我弹出了viewController并再次使用tableView切换到这个viewController。图像将显示。我想(我猜)是因为
这些图片还没有全部下载完毕。所以,我看不到图像。但是如果我弹出viewController并加载viewController的对象,viewController从缓存中获取图像。因此,可以显示图像
我想知道如何避免这个问题?谢谢
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell
let row = indexPath.row
cell.content.text = messages[row].content
cell.date.text = messages[row].createdDateStrInLocal
cell.messageOwner.text = messages[row].user
if let avatar = cacheImage.object(forKey: messages[row].user as NSString){
cell.profileImageView.image = avatar
} else {
fetchUserAvatar(avatarName: messages[row].user, handler: { [unowned self] urlStr in
if let url = URL(string: urlStr), let data = try? Data(contentsOf: url), let avatar = UIImage(data: data){
self.cacheImage.setObject(avatar, forKey: self.messages[row].user as NSString)
cell.profileImageView.image = avatar
}
})
}
return cell
}
fileprivate func fetchUserAvatar(avatarName: String, handler: @escaping (String) -> Void){
guard !avatarName.isEmpty, let user = self.user, !user.isEmpty else { return }
let url = URL(string: self.url + "/userAvatarURL")
var request = URLRequest(url: url!)
let body = "username=" + user + "&avatarName=" + avatarName
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = body.data(using: .utf8)
defaultSession.dataTask(with: request as URLRequest){ data, response, error in
DispatchQueue.main.async {
if let httpResponse = response as? HTTPURLResponse {
if 200...299 ~= httpResponse.statusCode {
print("statusCode: \(httpResponse.statusCode)")
if let urlStr = String(data: data!, encoding: String.Encoding.utf8), urlStr != "NULL" {
handler(urlStr)
}
} else {
print("statusCode: \(httpResponse.statusCode)")
if let unwrappedData = String(data: data!, encoding: String.Encoding.utf8) {
print("POST: \(unwrappedData)")
self.warning(title: "Fail", message: unwrappedData, buttonTitle: "OK", style: .default)
} else {
self.warning(title: "Fail", message: "unknown error.", buttonTitle: "OK", style: .default)
}
}
} else if let error = error {
print("Error: \(error)")
}
}
}.resume()
}
我修改了它,并在ViewDiLoad中移动下载代码并重新加载tableview,结果是一样的。您的图像视图有固定大小,还是利用了固有大小?根据你的描述,我认为是后者。更新缓存并在
fetchUserAvatar
完成处理程序中重新加载单元格应该可以解决这个问题
但这里有两个问题:
dataTask
来检索图像,而不是数据(contentsOf:)
,因为前者是异步的,后者是同步的。而且您永远不希望在主队列上执行同步调用。充其量,此同步网络调用会对滚动的平滑度产生不利影响。在最坏的情况下,如果网络请求因任何原因而减慢,并且你在错误的时间阻止了主线程,你就有可能让看门狗进程杀死你的应用程序
就我个人而言,我会让fetchUserAvatar
异步执行第二个异步请求,并更改闭包以返回UIImage
,而不是作为字符串的URL
也许是这样的:
fileprivate func fetchUserAvatar(avatarName: String, handler: @escaping (UIImage?) -> Void){
guard !avatarName.isEmpty, let user = self.user, !user.isEmpty else {
handler(nil)
return
}
let url = URL(string: self.url + "/userAvatarURL")!
var request = URLRequest(url: url)
let body = "username=" + user + "&avatarName=" + avatarName
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = body.data(using: .utf8)
defaultSession.dataTask(with: request) { data, response, error in
guard let data = data, error == nil, let httpResponse = response as? HTTPURLResponse, 200...299 ~= httpResponse.statusCode else {
print("Error: \(error?.localizedDescription ?? "Unknown error")")
DispatchQueue.main.async { handler(nil) }
return
}
guard let string = String(data: data, encoding: .utf8), let imageURL = URL(string: string) else {
DispatchQueue.main.async { handler(nil) }
return
}
defaultSession.dataTask(with: imageURL) { (data, response, error) in
guard let data = data, error == nil else {
DispatchQueue.main.async { handler(nil) }
return
}
let image = UIImage(data: data)
DispatchQueue.main.async { handler(image) }
}.resume()
}.resume()
}
单元格。单元格可能已从视图中滚出,您可能正在为表格的另一行更新单元格。这可能只对速度非常慢的网络连接有问题,但仍然是一个问题
异步闭包应该确定单元格的索引路径,然后使用重新加载该索引路径
例如:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell
let row = indexPath.row
cell.content.text = messages[row].content
cell.date.text = messages[row].createdDateStrInLocal
cell.messageOwner.text = messages[row].user
if let avatar = cacheImage.object(forKey: messages[row].user as NSString){
cell.profileImageView.image = avatar
} else {
cell.profileImageView.image = nil // make sure to reset this first, in case cell is reused
fetchUserAvatar(avatarName: messages[row].user) { [unowned self] avatar in
guard let avatar = avatar else { return }
self.cacheImage.setObject(avatar, forKey: self.messages[row].user as NSString)
// note, if it's possible rows could have been inserted by the time this async request is done,
// you really should recalculate what the indexPath for this particular message. Below, I'm just
// using the previous indexPath, which is only valid if you _never_ insert rows.
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
return cell
}
坦率地说,这里还有其他一些微妙的问题(例如,如果您的用户名或化身名称包含保留字符,您的请求将失败;如果您在非常慢的连接上快速滚动,可见单元格的图像将在不再可见的单元格后面积压,您有超时的风险,等等)。不要花太多时间考虑如何修复这些更微妙的问题,你可以考虑使用一个已建立的<代码> UIIVIEVIEW 类来执行异步图像请求并支持缓存。典型的选项包括AlamofireImage、KingFisher、SDWebImage等。
fetchUserAvatar(avatarName:handler:(String)->Void)
主线程中是否有处理程序:
闭包?我想你需要在获得avatar后重新加载单元格。我建议查看SDWebImage。我添加了更多详细信息。但是我应该在哪里添加重新加载?对不起,第一点我不太明白。正如您在fetchUserAvatar
中所说的获取图像,那么方法中有两个请求?@CarlHung-是的,两个请求,一个获取URL,一个获取图像。理想情况下,如果您可以将web服务更改为仅返回实际图像作为原始请求的一部分,则效率会更高。但是,正如您现在设计的web服务一样,一个URL请求随后必须在另一个请求中检索,那么,是的,您应该让fetchUserAvatar
执行这两个请求。请参阅上面修订答案中的示例。我重写了该方法,并在ViewDidLoad中使用此新方法下载图像。是一样的。无法修复它。我需要打开VC,然后再继续。然后,我将看到它。在viewDidLoad
中?通常,我们建议延迟获取图像(在cellForRowAt
中)。我无法评论你在viewDidLoad
中的尝试,除非看到你做了什么。当viewDidLoad
获取完相关图像后,如何触发相关单元格的重新加载?哦,我误解了。我认为“积压”意味着最好把它拿到别的地方。我将代码放回tableView(:,cellForRowAt:)它可以工作。虽然我遇到了布局问题。这是另一回事。谢谢