图像无法在表格视图中显示

Posted

技术标签:

【中文标题】图像无法在表格视图中显示【英文标题】: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(第三方库) 【参考方案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.

【讨论】:

你能告诉我更多关于dispatch_group_t的信息吗? @CEz 我根据要求添加了一个带有 cmets 的示例。这也是您的参考:developer.apple.com/reference/dispatch/dispatch_group_t

以上是关于图像无法在表格视图中显示的主要内容,如果未能解决你的问题,请参考以下文章

表格视图单元格中的滚动视图将无法正确显示图像,并且在 swift 4 中的分页模式下有一个额外的页面

在表格视图单元格中添加集合视图 swift 3

无法在自定义表格视图单元格中使 ImageView 成为一个圆圈

图像未显示在表格视图中

在 ios 的表格视图单元格中显示图像

在保存在文档目录中的表格视图中显示图像