【问题标题】:images can't be display in table view图像无法在表格视图中显示
【发布时间】:2017-05-11 06:26:12
【问题描述】:

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

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

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

然而,即使我在tableView控制器中创建了一个模型类的实例,并在viewDidlLoad中调用mainPulatesData(),图像也没有出现在表格中。

根据表格视图控制器类中的其他打印消息,我发现它甚至可以添加到模型类中的数组中,但似乎不适用于 tableView 控制器中的模型类实例。

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

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
}

【问题讨论】:

  • 是延迟加载的概念。为此,您可以使用 SDWebImage(第三方库)

标签: swift uitableview nsurlsessiondatatask


【解决方案1】:

原因是图像或imageList 在你reloadData() 之后调用cellForRowAt 时还没有准备好。

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

// 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()
  }
}

【讨论】:

  • 感谢您的建议,但要求使用模型类处理数据,控制器对数据一无所知。在这种情况下,我该怎么办?
  • @CEz 我已经更新了我的答案。希望这就是你想要的。
【解决方案2】:

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

我正在使用的处理程序的简单示例,我在我的viewDidLoad 中调用它,它从服务器请求数据并返回一个[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)

【讨论】:

  • 你能给出一些示例代码吗?我不熟悉协议和完成处理程序。顺便说一句,它被要求处理模型类中的数据,控制器不应该知道任何关于数据的事情。
  • @CEz 添加了一个例子
  • 你的意思是我需要用class func requestAllReservations 方法创建一个名为ReservationTableModule 的类吗?然后打电话给viewDidLoad()?
  • @CEz 不,这是一个如何使用回调函数的示例
【解决方案3】:

您应该注意这里的函数调用顺序:

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.
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多