Search code examples
swiftuitableviewnsurlsessiondatatask

images can't be display in table view


I created a tableView with custom cells that each cell has an image.

In the model class, I created a func mainPulatesData() to use URLSession dataTask method to retrieve data from url, and convert data into UIImage in the completion handler block, then add image into an variable of array of UIImage.

The process of retrieve data and adding them into UIImage array was perform in DispatchQueue.global(qos: .userInitiated).async block. based on the print message, the images did be added into array.

however, even I created an instance of model class in tableView controller, and invokes the mainPulatesData() in viewDidlLoad, the image didn't show up in the table.

Based on other print message in table view controller class, I found even it can be added into array in model class, but it seems like doesn't work on the instance of model class in tableView controller.

that's the code in model class to gain image data:

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

that's the code in view controller to create instance of model:

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
}

Solution

  • The reason is that the images or imageList isn't ready by the time cellForRowAt is called after you reloadData().

    A good practice is to use placeholder images in the beginning and only load image when a table view cell is visible instead of everything at once. Something like:

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