图片无法在 table 视图中显示

images can't be display in table view

我创建了一个 table带有自定义单元格的视图,每个单元格都有一个图像。

在模型 class 中,我创建了一个 func mainPulatesData() 以使用 URLSession dataTask 方法从 url 中检索数据,并将数据转换为 UIImage在完成处理程序块中,然后将图像添加到 UIImage 数组的变量中。

检索数据并将它们添加到 UIImage 数组的过程在 DispatchQueue.global(qos: .userInitiated).async 块中执行。根据打印消息,图像确实已添加到数组中。

但是,即使我在 tableView controller 中创建了模型 class 的实例,并在 viewDidlLoad 中调用了 mainPulatesData(),图像也没有显示在 table.

根据 table 视图控制器 class 中的其他打印消息,我发现它甚至可以添加到模型 class 中的数组中,但它似乎不起作用table视图控制器中模型 class 的实例。

这是模型 class 中获取图像数据的代码:

func mainPulatesData() {
    let session = URLSession.shared
    if myURLs.count > 0{
        print("\(myURLs.count) urls")
        for url in myURLs{
            let task = session.dataTask(with: url, completionHandler: { (data,response, error)  in
                let imageData = data
                DispatchQueue.global(qos: .userInitiated).async {
                    if imageData != nil{
                        if let image = UIImage(data: imageData!){
                            self.imageList.append(image)
                            print("\(self.imageList.count) images added.")
                        }
                    }
                    else{
                        print("nil")
                    }
                }
            })
            task.resume()   
        }
    }
}

这是视图控制器中创建模型实例的代码:

override func viewDidLoad() {
    super.viewDidLoad()
    myModel.mainPulatesURLs()
    myModel.mainPulatesData()
    loadImages()
}

private func loadImages(){
    if myModel.imageList.count > 0{
        tableView.reloadData()
    }
    else{
        print("data nil")
    }
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return myModel.imageList.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
    if myModel.imageList.count > 0{
        let image = myModel.imageList[indexPath.row]
        cell.tableImage = image
        return cell

    }
    return cell
}

您应该注意此处函数调用的顺序:

myModel.mainPulatesURLs() --> populates myURLs
myModel.mainPulatesData() --> populates image from myURLs in forloop asynchronously.
loadImages() --> called asynchronously.

当您从 myModel.mainPulatesData() 加载图像时,您已经调用了 loadImages(),其中 myModel.imageList 仍然是空的。

您应该在 myModel.mainPulatesData() 回调后或您确定图像已加载时调用 loadImages()

您可以使用 dispatch_group_t 配置回调。

此处按要求:

import UIKit


var myURLs: [String] = ["urlA", "urlB", "urlC", "urlD"]

// we define the group for our asynchronous fetch. also works in synchronous config
var fetchGroup = DispatchGroup()

for urlString in myURLs {

    // for every url fetch we define, we will call an 'enter' to issue that there is a new block for us to wait or monitor
    fetchGroup.enter()

    // the fetch goes here
    let url = URL(string: urlString)!
    URLSession.shared.downloadTask(with: URLRequest(url: url), completionHandler: { (urlReq, urlRes, error) in

        // do your download config here...

        // now that the block has downloaded the image, we are to notify that it is done by calling 'leave'
        fetchGroup.leave()
    }).resume()
}

// now this is where our config will be used.
fetchGroup.notify(queue: DispatchQueue.main) { 

    // reload your table here as all of the image were fetched regardless of download error.
}

图片不显示是因为你在后台线程(异步)下载它们,并且loadImages()被同步调用。这意味着 loadImage()myModel.mainPulatesData() 执行之前被调用,所以当您的图像被下载时,tableview 没有被更新(reloadData() 没有被调用)。您应该创建 Protocol 以通知 UIViewController,数据已下载,或使用 Completion Handler.

我正在使用的处理程序的简单示例,我在我的 viewDidLoad 中调用它,它请求来自服务器的数据和 return [Reservation]?

的数组
 ReservationTableModule.requestAllReservations { [weak self] reservations in
            guard let `self` = self else {
                return
            }

            guard let `reservations` = `reservations` else {
                return
            }
            self.reservations = reservations
            .reservationsTableView.reloadData()
        }

这是实际的请求函数

class func requestAllReservations(handler: @escaping ([Reservation]?) -> Void) {

    let url = "reservations/all"

    APIModel.shared.requestWithLocation(.post, URL: url, parameters: nil) { data in
        let reservations = data?["reservations"].to(type: Reservation.self) as? [Reservation]
        handler(reservations)
    }
}

handler: @escaping ([Reservation]?) -> Void 被称为完成处理程序,我想你应该做到 handler: @escaping ([UIImage]?) -> Void 并在你的数据下载后调用 handler(reservations)

原因是图像或 imageList 在您 reloadData() 之后调用 cellForRowAt 时尚未准备好。

一个好的做法是在开始时使用占位符图像,并且仅在 table 视图单元格可见时才加载图像,而不是一次加载所有内容。类似于:

// VC class
private var modelList = [MyModel(url: url1), MyModel(url: url2), ...]

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return modelList.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath) as! ImageTableViewCell
    cell.update(model: modelList[indexPath.row])
    return cell
}

// Cell class
@IBOutlet weak var cellImageView: UIImageView!

func update(model: MyModel) {
    model.fetchImage(callback: { image in
        self.cellImageView.image = image
    })
}

// Model class
final class MyModel: NSObject {
  let url: URL
  private var _imageCache: UIImage?

  init(url: URL) {
    self.url = url
  }

  func fetchImage(callback: @escaping (UIImage?) -> Void) {
    if let img = self._imageCache {
      callback(img)
      return
    }

    let task = URLSession.shared.dataTask(with: self.url, completionHandler: { data, _, _  in
      if let imageData = data, let img = UIImage(data: imageData) {
        self._imageCache = img
        callback(img)
      } else {
        callback(nil)
      }
    })
    task.resume()
  }
}