Ios 无法对tableView单元格图像重新排序
为了便于学习,我正在创建一个应用程序来显示一些星球大战飞船的列表。它为ship对象获取我的json(本地)(本例中有4个ship)。 它正在为表视图使用自定义单元格 如果我已经下载了图像(在用户文档中)或没有下载图像,则表填充不会出现问题。 我的starshipData数组由我的DataManager类按委托填充。 我删除了一些代码以使类更小,如果需要,我可以显示所有内容 好的,当我按下排序按钮时,问题就发生了(很少发生)。 我的做法是在恢复或下载图像后,更新starshipData数组中的图像字段 这是我的排序方法,非常基本Ios 无法对tableView单元格图像重新排序,ios,swift,uitableview,completionhandler,dispatch-async,Ios,Swift,Uitableview,Completionhandler,Dispatch Async,为了便于学习,我正在创建一个应用程序来显示一些星球大战飞船的列表。它为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()
}
}
总而言之,当您打开应用程序时,以下是无序表:
按下排序按钮后,预期结果(大部分时间发生):
按下排序按钮后出现错误结果(很少发生):
如果需要的话,我很乐意添加更多信息,泰首先,考虑为UITabeVIEW单元类移动单元格配置。大概是这样的:
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)
}
}
}