Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 在tableView中下载图像(:,cellForRowAt:)_Ios_Swift_Uitableview - Fatal编程技术网

Ios 在tableView中下载图像(:,cellForRowAt:)

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

当我使用tableView切换到viewController时,tableviewcell立即使用方法
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:)它可以工作。虽然我遇到了布局问题。这是另一回事。谢谢