William Loke
William Loke

Reputation: 377

Cell not showing image after loaded from Firebase Swift

I have a UITableViewCell which shows either an image or text. I have set my UIImageView height and width to <= 150 so that it wouldn't affect the cell height in case the image isn't present but the text is. (this is a messaging app)

Now my problem is, the cell does not show the image after it's loaded and user needs to go back, and view this screen again in order to see the image. How should i solve this?

Before Image is loaded:

enter image description here

After the image is successfully loaded:

enter image description here

as u can see, it doesnt show, only the view was expanded

If user presses on BACK and comes again in this view,

enter image description here

the image now shows.

how should i solve this issue? because I am not able to write tableview.reloaddata() in tableviewcell.

for my code in my tableviewcell to configure my cell:

func configCell(message: Message) {

    self.message = message

    if message.fromId == currentUser {

        sentView.isHidden = false

        sentMsgLabel.text = message.textMessages

        receivedMsgLabel.text = ""

        receivedView.isHidden = true

        timeReceived.text = ""

        timeSent.text = message.timestamp

        youLabel.text = "You"

        themLabel.text = ""


        if let ImageUrl = message.imageUrlLink {

            self.receivedimg.loadImageUsingCacheWithUrlString(ImageUrl)
        }
        }

        else {

        sentView.isHidden = true

        sentMsgLabel.text = ""

        receivedMsgLabel.text = message.textMessages

        receivedMsgLabel.isHidden = false

        timeReceived.text = message.timestamp

        timeSent.text = ""

        // setting name for receipient
        Database.database().reference().child("users").child(message.fromId!).child("name").observe(.value) { (datasnapshot) in
        if let name = datasnapshot.value as? String {
                self.themLabel.text = name
            }
        }

        youLabel.text = ""
    }
}

for my loadImageUsingCacheWithUrlString code would be an extension as below:

let imageCache = NSCache<AnyObject, AnyObject>()

    extension UIImageView {

    func loadImageUsingCacheWithUrlString(_ urlString: String) {

        self.image = nil

        //check cache for image first
        if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
            self.image = cachedImage
            return
        }

        //otherwise fire off a new download
        let url = URL(string: urlString)
        URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in

            //download hit an error so lets return out
            if error != nil {
                print(error ?? "")
                return
            }

            DispatchQueue.main.async(execute: {

                if let downloadedImage = UIImage(data: data!) {
                    imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)

                    self.image = downloadedImage
                }
            })

        }).resume()
    }

}

Upvotes: 0

Views: 1185

Answers (5)

Ricardo Montesinos
Ricardo Montesinos

Reputation: 74

I had the same issue and this is how I resolved it.

Inside your ViewController create a function that will update each cell dynamically:

private func reloadRowAt(_ indexPath: IndexPath) {
    self.tableView.beginUpdates()
    self.tableView.reloadRows( at: [indexPath], with: .fade)
    self.tableView.endUpdates()
}

Now use it inside cellForRowAtindexPath() use it:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: identifier) as? MyCustomCell else { return UITableViewCell()}

   // Download and refresh cell
   let image = imageDownloader.loadImage(from: url) {
       self.reloadRowAt(indexPath) 
   }
   cell.image = image
   return cell
  }

Now look in the code this { self.reloadRowAt(indexPath) }, this is a completion handler of the method that downloads the image, once the image is downloaded it returns to the cell.

func loadImage(from url: URL, completion: @escaping () -> Void ) -> UIImage {
    let size = CGSize(width: 100, height: 140)
    // Download and Compress image to fit container size
    DispatchQueue.global(qos: .background).async {
      guard let newimage = self.downloadAndCompress(url: url, newSize: size) else { return }
      // Show in UI
      DispatchQueue.main.async {
        completion()
      }
    }
    return UIImage()
  }

In addition, I compressed the downloaded image into a new size with the method self.downloadAndCompress.

See full code:

https://gist.github.com/richimf/fad8d320fbfb5059c5472e0f55ea24c2

Upvotes: 0

William Loke
William Loke

Reputation: 377

I have tried so many ways, in the end..In my ViewDidload, I just added a delay

 DispatchQueue.main.asyncAfter(deadline: .now() + 1){
        self.tableView.reloadData()
    }

Delay is required. Without it, it doesn't works. I am assuming it's due to the cell heights were set up and loaded before the image is finished loading, so the tablecell didn't know how much height was needed, that is why we need to reload the tableview after 1 second delay

Upvotes: 1

Daniel Dramond
Daniel Dramond

Reputation: 1598

It seems to be that this code is being executed once but your UITableView is still loading the cell anyway as the count is clearly 2. When you pass in your Message object to your UICollectionViewCell class inside of your cellForRowAt indexPath method, you'll want to use a didSet on that message and THEN call your function. This means that every time a message is passed in and when it is passed in, whatever is inside the didSet will be executed.

Inside your UICollectionViewCell class:

var message: Message? {
    didSet {
        configCell(message)
    }
}

Class with your UITableViewController:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = // your cell here dequeue cell and downcast as your cell class e.g as! MyCell

    cell.message = yourMessagesArray[indexPath.row]

    return cell
}

Upvotes: 0

Ganesh Manickam
Ganesh Manickam

Reputation: 2139

Instead of doing action into the UITableViewCell do action inside cellForRowAt

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //Your actuions here
}

Then you can use tableView.reloadData() inside your UITableViewController

Hope this will help to solve your problem.

Upvotes: 0

tphduy
tphduy

Reputation: 126

Your problem clearly after downloading image, your cell is not updated right the time, try self.setNeedDisplays() inside your cell. It tell your cell should update in next view cycle.

Instead of loading and catching yourself, I recommend using KingFisher framework, it do almost everything we need on working with image from remote server: downloading, catching, prefetching ... and show your image immediately after downloading

Upvotes: 1

Related Questions