Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/104.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

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/http/4.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单元格图像重新排序_Ios_Swift_Uitableview_Completionhandler_Dispatch Async - Fatal编程技术网

Ios 无法对tableView单元格图像重新排序

Ios 无法对tableView单元格图像重新排序,ios,swift,uitableview,completionhandler,dispatch-async,Ios,Swift,Uitableview,Completionhandler,Dispatch Async,为了便于学习,我正在创建一个应用程序来显示一些星球大战飞船的列表。它为ship对象获取我的json(本地)(本例中有4个ship)。 它正在为表视图使用自定义单元格 如果我已经下载了图像(在用户文档中)或没有下载图像,则表填充不会出现问题。 我的starshipData数组由我的DataManager类按委托填充。 我删除了一些代码以使类更小,如果需要,我可以显示所有内容 好的,当我按下排序按钮时,问题就发生了(很少发生)。 我的做法是在恢复或下载图像后,更新starshipData数组中的图像

为了便于学习,我正在创建一个应用程序来显示一些星球大战飞船的列表。它为ship对象获取我的json(本地)(本例中有4个ship)。 它正在为表视图使用自定义单元格

如果我已经下载了图像(在用户文档中)或没有下载图像,则表填充不会出现问题。 我的starshipData数组由我的DataManager类按委托填充。 我删除了一些代码以使类更小,如果需要,我可以显示所有内容

好的,当我按下排序按钮时,问题就发生了(很少发生)。 我的做法是在恢复或下载图像后,更新starshipData数组中的图像字段

这是我的排序方法,非常基本

@objc private func sortByCost(sender: UIBarButtonItem) {
        starshipData.sort { $0.costInCredits < $1.costInCredits }
        starshipTableView.reloadData()
}
在这里,我使用willDisplay方法下载或获取图像,基本上是较重的数据

    // MARK: -> willDisplay
    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        // update cell image
        let cell = cell as! StarshipCell
        let imageUrl = starshipData[indexPath.row].imageUrl
        let starshipName = starshipData[indexPath.row].name
        let index = indexPath.row
        
        // if there isn't any image on the cell, proceed to manage the image
        if cell.starshipImgView.image == nil {
            // only instantiate spinner on imageView position if no images are set
            let spinner = UIActivityIndicatorView(style: .medium)
            startSpinner(spinner: spinner, cell: cell)

            // manage the image
            imageManager(starshipName: starshipName, imageUrl: imageUrl, spinner: spinner, cell: cell, index: index) { (image) in
                self.addImageToCell(cell: cell, spinner: spinner, image: image)
            }
        }
    }
我认为问题出在这里,因为我对swift和后台线程的了解仍在开发中

我在打印日志中发现,单元格未显示正确图像的时间是因为数组没有该索引的图像,因此单元格显示上次填充/加载表时的图像

我想知道这是否是因为在用户按下排序按钮之前,后台线程没有足够的时间用获取/下载的图像更新starshipArray

问题是,如果第一次正确填充了表,当按下排序按钮时,starshipData数组应该已经有了所有图像,正如您在imageManager方法中看到的,在图像从文档中展开后,我调用updateArrayImage来更新图像

也许是调度队列的使用量?是否正确使用了完成处理程序和dispatchQueues

    private func imageManager(starshipName: String, imageUrl: URL?, spinner: UIActivityIndicatorView, cell: StarshipCell, index: Int, completion: @escaping (UIImage) -> Void) {
        // if json has a string on image_url value
        if let unwrappedImageUrl = imageUrl {
            // open a background thread to prevent ui freeze
            DispatchQueue.global().async {
                // tries to retrieve the image from documents folder
                let imageFromDocuments = self.retrieveImage(imageName: starshipName)
                
                // if image was retrieved from folder, upload it
                if let unwrappedImageFromDocuments = imageFromDocuments {


// TO FORCE THE PROBLEM DESCRIBED, PREVENT ONE SHIP TO HAVE IT'S IMAGE UPDATED
//                    if (starshipName != "Star Destroyer") { 
                        self.updateArrayImage(index: index, image: unwrappedImageFromDocuments)
//                    }
                    
                    completion(unwrappedImageFromDocuments)
                }
                    // if image wasn't retrieved or doesn't exists, try to download from the internet
                else {
                    var image: UIImage?
                    self.downloadManager(imageUrl: unwrappedImageUrl) { data in
                        // if download was successful
                        if let unwrappedData = data {
                            // convert image data to image
                            image = UIImage(data: unwrappedData)
                            if let unwrappedImage = image {
                                self.updateArrayImage(index: index, image: unwrappedImage)
                                // save images locally on user documents folder so it can be used whenever it's needed
                                self.storeImage(image: unwrappedImage, imageName: starshipName)
                                completion(unwrappedImage)
                            }
                        }
                            // if download was not successful
                        else {
                            self.addImageNotFound(spinner: spinner, cell: cell)
                        }
                    }
                }
            }
        }
            // if json has null on image_url value
        else {
            addImageNotFound(spinner: spinner, cell: cell)
        }
    }
如果需要,下面是我在imageManager上使用的一些辅助方法

// MARK: - Helper Methods
    private func updateArrayImage(index: Int, image: UIImage) {
        // save image in the array so it can be used when cells are sorted
        self.starshipData[index].image = image
    }
    
    private func downloadManager(imageUrl: URL, completion: @escaping (Data?) -> Void) {
        let session: URLSession = {
            let configuration = URLSessionConfiguration.default
            configuration.timeoutIntervalForRequest = 5
            return URLSession(configuration: configuration, delegate: nil, delegateQueue: nil)
        }()
        
        var dataTask: URLSessionDataTask?
        dataTask?.cancel()
        dataTask = session.dataTask(with: imageUrl) { [weak self] data, response, error in
            defer {
                dataTask = nil
            }
            if let error = error {
                // use error if necessary
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
            else if let response = response as? HTTPURLResponse,
                response.statusCode != 200 {
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
            else if let data = data,
                let response = response as? HTTPURLResponse,
                response.statusCode == 200 { // Ok response
                DispatchQueue.main.async {
                    completion(data)
                }
            }
        }
        dataTask?.resume()
    }
    
    private func addImageNotFound(spinner: UIActivityIndicatorView, cell: StarshipCell) {
        spinner.stopAnimating()
        cell.starshipImgView.image = #imageLiteral(resourceName: "ImageNotFound")
    }
    
    private func addImageToCell(cell: StarshipCell, spinner: UIActivityIndicatorView, image: UIImage) {
        DispatchQueue.main.async {
            spinner.stopAnimating()
            cell.starshipImgView.image = image
        }
    }
    
    private func imagePath(imageName: String) -> URL? {
        let fileManager = FileManager.default
        // path to save the images on documents directory
        guard let documentPath = fileManager.urls(for: .documentDirectory,
                                                  in: FileManager.SearchPathDomainMask.userDomainMask).first else { return nil }
        let appendedDocumentPath = documentPath.appendingPathComponent(imageName)
        return appendedDocumentPath
    }
    
    private func retrieveImage(imageName: String) -> UIImage? {
        if let imagePath = self.imagePath(imageName: imageName),
            let imageData = FileManager.default.contents(atPath: imagePath.path),
            let image = UIImage(data: imageData) {
            return image
        }
        return nil
    }
    
    private func storeImage(image: UIImage, imageName: String) {
        if let jpgRepresentation = image.jpegData(compressionQuality: 1) {
            if let imagePath = self.imagePath(imageName: imageName) {
                do  {
                    try jpgRepresentation.write(to: imagePath,
                                                options: .atomic)
                } catch let err {
                }
            }
        }
    }
    
    private func startSpinner(spinner: UIActivityIndicatorView, cell: StarshipCell) {
        spinner.center = cell.starshipImgView.center
        cell.starshipContentView.addSubview(spinner)
        spinner.startAnimating()
    }
    
}
总而言之,当您打开应用程序时,以下是无序表:

按下排序按钮后,预期结果(大部分时间发生):

按下排序按钮后出现错误结果(很少发生):


如果需要的话,我很乐意添加更多信息,泰
class StarshipCell {

    private var starshipNameLabel = UILabel()
    private var starshipImgView = UIImageView()

    func configure(with model: Starship) {
       starshipNameLabel.text = model.name
       starshipImgView.downloadedFrom(link: model.imageUrl)
    }
}
调用
tableView(u:cellForRowAt:)中的
configure(with:Starship)
方法。

configure(with:Starship)
中调用的
downloadefrom(link:)
方法通过以下扩展提供

extension UIImageView {
    func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
        contentMode = mode
        URLSession.shared.dataTask(with: url) { data, response, error in
        guard let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
            let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
            let data = data, error == nil,
            let image = UIImage(data: data)
            else { return }
            DispatchQueue.main.async() {
                self.image = image
            }
        }.resume()
    }

    func downloadedFrom(link: String?, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
        if let link = link {
            guard let url = URL(string: link) else { return }
            downloadedFrom(url: url, contentMode: mode)
        }
    }
}

谢谢你提供的信息。我有个问题。在cellForRowAt方法中调用configure将使每个单元格都可以下载图像,即使它们没有显示。会发生这样的事吗?我知道在这个例子中我只有4艘船,但我想知道如果有1000艘船的话会发生什么。还有一件事,你能告诉我我的例子做错了什么吗?我想知道使用willDisplay方法是否是错误的。-只有当单元格出现时才会调用方法
tableView(\u:cellForRowAt:)。
。此外,如果你有1000个元素,你的内存中就没有1000个图像特别是我从未使用过willDisplay方法。我读了文档,但它是一个安静的混乱。我建议您不要为此使用willDisplay。对不起,我心里没有任何例子。最后,您可以阅读这篇文章,阐明表视图是如何工作的。如果这能解决您的问题,请将答案标记为解决方案。嗨,朋友,我花了一些时间来测试。是的,你的例子似乎解决了这个问题,我将标记为解决方案。我还尝试将willDisplay代码移动到cellForRowAt方法,在json中添加了更多的对象,甚至尝试在其他特定位置添加/删除一些DispatchQueue,大量测试正在进行。我仍在确保一切顺利,并遵循一些指导如何做到这一点。谢谢你的帮助!
extension UIImageView {
    func downloadedFrom(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
        contentMode = mode
        URLSession.shared.dataTask(with: url) { data, response, error in
        guard let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
            let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
            let data = data, error == nil,
            let image = UIImage(data: data)
            else { return }
            DispatchQueue.main.async() {
                self.image = image
            }
        }.resume()
    }

    func downloadedFrom(link: String?, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
        if let link = link {
            guard let url = URL(string: link) else { return }
            downloadedFrom(url: url, contentMode: mode)
        }
    }
}