Reputation: 377
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:
After the image is successfully loaded:
as u can see, it doesnt show, only the view was expanded
If user presses on BACK and comes again in this view,
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
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
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
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
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
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