the data mix in table view, how do I avoid it? swift - swift

I have some troubles, I have a table view with a custom cell, the cell has a image view, the image is downloaded from internet, when the data loaded the first time everything is fine, but if scroll quickly the screen, the images will mix.
this is my code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell:FlinkerTableViewCell = tableView.dequeueReusableCell(withIdentifier: "flinkerCell") as! FlinkerTableViewCell
cell.imgeCell.image = nil
let row = indexPath.row
let flinkerAux: Flinker = flinkers[row]
if let stringURL = flinkerAux.image{
if stringURL != "" {
DispatchQueue.main.async {
cell.imgeCell.downloadedFrom(link: stringURL)
cell.imgeCell.contentMode = .scaleAspectFill
}
cell.imgeCell.setNeedsDisplay()
cell.lblTitle.text = flinkerAux.fullName
cell.lblSubtitle.text = flinkerAux.phone
}else{
cell.imgeCell.image = UIImage(named: "user_placeholder")
cell.lblTitle.text = flinkerAux.fullName
cell.lblSubtitle.text = flinkerAux.phone
}
}else{
cell.imgeCell.image = UIImage(named: "user_placeholder")
cell.lblTitle.text = flinkerAux.fullName
cell.lblSubtitle.text = flinkerAux.phone
}
cell.imgeCell.layer.cornerRadius = 20
cell.imgeCell?.clipsToBounds = true
cell.selectionStyle = .none
return cell
}
I'm using swift 4.2, I tried with DispathQueue load the images, but when the table view has so much images, the problem is back.
how I should do?

This is commonly known issue. I'm sure you can find a lot with google
The easiest option here is to use any library to download images, like AlamofireImage, Nuke, Kingfisher, SDWebImage
UITableViewCell asynchronously loading images issue - Swift

Related

Populate UICollectionView with images from CoreData

I am trying to populate my UICollectionView with my data from CoreData database. The problem is that I want to show a photo in Collection Cell and using data to create UIImage - this task can take a while. With the current solution the images are loaded approx. after 3 seconds but all the other data is already shown in collection view.
How should I add the loading overlay and know when all the images are ready to hide it, or what is correct approach?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier, for: indexPath) as? LocationCollectionViewCell {
cell.name.text = viewModel.locations[indexPath.row].name
cell.unlocked.text = viewModel.locations[indexPath.row].unlocked ? "Unlocked" : "Locked"
if let data = viewModel.locations[indexPath.row].image {
DispatchQueue.global(qos: .background).async {
let image = UIImage(data: data)
DispatchQueue.main.async {
cell.image.image = self.viewModel.locations[indexPath.row].unlocked ? image : image?.grayscale()
}
}
} else {
cell.image.image = viewModel.locations[indexPath.row].unlocked ? UIImage(named: "noun_Akropolis_403786") : UIImage(named: "noun_Akropolis_403786")?.grayscale()
}
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: viewModel.reuseIdentifier, for: indexPath)
return cell
}
}
So #peter you can try follow things:
You can add loader in cell and show/hide it when image is nil or not.
For that create an array where when any image is loaded just append that index there. Until array doesnt contain all index show loader in screen else hide loader.
For handling error you can simply use try catch. If image is not loading or it is falied then you shouldn provide dummy or placeholder image. So that loader will be removed at one time.

Loading an Image file from Firebase Storage using SDWebImage

I am trying to load an image from my Firebase Storage to be displayed on my app view.
I have 2 classes mainly
Main "ClassView" that includes a tableView.
A custom tableViewCell - "ClassCell" (This class includes an imageView where I want to display the picture from firestore. - imageView is called classImage.
Below is the tableView Data Source Extension.
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: K.tableCellNibName, bundle: nil), forCellReuseIdentifier: K.tableCellIdentifier)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let storageRef = storage.reference()
let reference = storageRef.child(K.FStore.classImagesPath)
let placeholderImage = UIImage(named: "placeholder.png")
let imageView: UIImageView = self.imageView
let cell = tableView.dequeueReusableCell(withIdentifier: K.tableCellIdentifier, for: indexPath) as! ClassCell
//below is where I try to set the my image but it is not changing anything
cell.classImage?.sd_setImage(with: reference, placeholderImage: placeholderImage)
//This I tried to make sure that I am can access the image right and it worked just fine
// cell.classImage?.backgroundColor = .black
return cell
}
The Image is not displayed using the sd_setImage method. Any help would be highly appreciated if I have any error in my code or missing a declaration anywhere.
Below is what I am getting when I run the simulator. Instead of the image displayed in these black boxes, they are empty. Also, the imageView.image is returning nil so most probably the sd_setImage is not placing the image right.
self.imgSidebarMenuImage.sd_imageIndicator = SDWebImageActivityIndicator.whiteLarge
self.imgSidebarMenuImage.sd_setImage(with: URL(string: (person["image"] as! String)), placeholderImage: UIImage(named: "logo"))
try this
#1
let storage = Storage.storage().reference(withPath: “YOUR_PATH”)
storage.downloadURL { (url, error) in
if error != nil {
print((error?.localizedDescription)!)
return
}
print(“Download success”)
//url = your will get an image URL
self.someImageURL = url!
}
#2
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// cell identifier methods
var item = itemData[indexPath.row]
let referenceImage: StorageReference = storageRef.child("YOUR_PATH")
let url = URL(string: referenceImage!)!
cell.itemImage?.sd_setImage(with: url, placeholderImage: #imageLiteral(resourceName: "placeholder"))
}
}
return cell
}

How to properly display pictures in a TableView from Firebase Storage in SWIFT

I have a chat app where people can talk in a group and a little picture is displayed in each cell to show who is talking. I managed to display these pictures from Firebase storage but it is not always the right picture which is displayed at the right place.
It only works when I go to the previous View Controller and coming back the chat View to see the pictures displayed properly in each cell.
I tried to use DispatchQueue.main.async {} probably not in the good way cause it did not work for me.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messageArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "customMessageCell", for: indexPath) as! CustomMessageCell
cell.selectionStyle = .none
// CHANGE TEXT ACCORDING TO SENDER
if message.sender == Auth.auth().currentUser?.email{
cell.messageBubble.backgroundColor = UIColor(red:0.30, green:0.68, blue:1.5, alpha:1.0)
// ...
} else {
cell.messageBubble.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
// ...
}
let theTimeStamp = messageArray[indexPath.row].createdAt
let doubleTime = Double(theTimeStamp)
let myDate = Date(timeIntervalSince1970: doubleTime )
let dateToShow = myDate.calenderTimeSinceNow()
cell.messageBodyTextView.text = messageArray[indexPath.row].messageBody
cell.usernameLabel.text = messageArray[indexPath.row].name
cell.timeLabel.text = dateToShow
let imagePath = self.storageRef.reference(withPath:"\(message.uid)/resizes/profilImage_150x150.jpg")
imagePath.getData(maxSize: 10 * 1024 * 1024) { (data, error) in
if let error = error {
cell.userPicture.image = UIImage(named: "emptyProfilPic")
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
print("Got an error fetching data : \(error.localizedDescription)")
return
}
if let data = data {
cell.userPicture.image = UIImage(data: data)
cell.userPicture.layer.cornerRadius = cell.userPicture.frame.height / 2
cell.userPicture.clipsToBounds = true
}
}
return cell
}
Thank you for your help !
You have to prepare the cell to be reusable with the proper override prepareForReuse().
For more clean code I suggest to you to implement the cells in separate cocoa Touch classes so it's easier to override and prepare for next data incoming, avoiding your problem.
What I mean it's a sort of this:
class mineCell:UITableViewCell {
#IBOutlet weak var text:UILabel!
#IBOutlet weak var img:UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
func updateCell(dataIn){
.
.
}
override func prepareForReuse() {
text.text = ""
img.image = nil
}
In your cellForRowAt table implementation just call the update function and pass your data like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "mineCell"
if let cell = mineTable.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? mineCell {
updateCell(dataIn)
return cell
}
return mineCell()
}
In this way you are always sure that your cell will be ready for every reuse and not loading wrong data from the cell above.
Just to let you know, the problem was thaT I was reloading the table View after each message loaded. Instead, the best solution was to add a row to the tableview without reloaded the tableview after each message :
self.ConvertationTableView.insertRows(at: [IndexPath(row: self.messageArray.count - 1, section: 0)], with: .automatic)

Swift TableView making multiple network requests for images

I have a table in swift. Whenever I scroll up and down the tableview it runs the getImageForCell function again even though the image has already been loaded. Is there a way for this not to happen. Below is my code.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewTableViewCell", for: indexPath) as! ReviewTableViewCell
let review = json[reviewType][indexPath.row]
cell.nameLabel.text = review[userType]["name"].stringValue
cell.reviewLabel.text = review["message"].stringValue
cell.dateLabel.text = review["created_at"].stringValue
cell.ratingStars.rating = Double(review["rating"].intValue)
getImageForCell(url: review[userType]["photo_url"].stringValue, cell: cell)
return cell
}
func getImageForCell(url: String, cell: ReviewTableViewCell) {
Alamofire.request(url).responseImage { response in
if let downloadedImage = response.result.value {
print("downloaded image \(downloadedImage)")
DispatchQueue.main.async {
cell.profileImageView.image = downloadedImage
}
}
}
}
This is due to both not cacheing the image, and also not reusing the cell - in your view you are recreating the cell each time - this would not specifically fix the image issue, but will improve performance. My favourite cache image extension is kingfisher (no affiliation), although alamofire can be used to cache images too.
https://github.com/onevcat/Kingfisher

Async images change every time while scrolling?

So I'm creating an iOS app that lets you browse through the Unsplash wallpapers and I used UICollectionView to load the images in cells but whenever I scroll through an image, I go back the image changes into a different one.
Here's the code
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell
let downloadQueue = dispatch_queue_create("com.donbytyqi.Papers", nil)
dispatch_async(downloadQueue) {
let imageURL = NSURL(string: "https://unsplash.it/200/300/?random")
let imageData = NSData(contentsOfURL: imageURL!)
var image: UIImage?
if imageData != nil {
image = UIImage(data: imageData!)
}
dispatch_async(dispatch_get_main_queue()) {
cell.imageView.image = image
}
}
return cell
}
EDIT: Two things going on:
collectionView.dequeueReusableCellWithReuseIdentifier reuses a cell that has already been created (if there's one available). So you're dequeueing one of your previous cells.
The URL your loading your images from generates a random image each time it is called.
Thus, when you scroll to the point where the first row of your collectionview is off screen, those cells get reused. Then when you scroll back up, the cells are recreated with a new random image from "https://unsplash.it/200/300/?random"
A way of circumventing this would be to keep an array of all your images indexed based on the cell index. Of course, if your images are very big and/or you have a really large collectionView, you may run out of memory.
Take a look at this code that I have mocked up. I have not verified that the code actually works.
//instance var to store your images
var imageArray: [UIImage]?
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell
// Check if we have already loaded an image for this cell index
if let oldImage: UIImage = imageArray[indexPath.row] {
cell.imageView.image = oldImage
return cell
} else {
// remove the old image, before downloading the new one
cell.imageView.image = nil
}
let downloadQueue = dispatch_queue_create("com.donbytyqi.Papers", nil)
dispatch_async(downloadQueue) {
let imageURL = NSURL(string: "https://unsplash.it/200/300/?random")
let imageData = NSData(contentsOfURL: imageURL!)
var image: UIImage?
if imageData != nil {
image = UIImage(data: imageData!)
// Save image in array so we can access later
imageArray.insert(image, atIndex: indexPath.row)
}
dispatch_async(dispatch_get_main_queue()) {
cell.imageView.image = image
}
}
return cell
}
#toddg solution is correct. But still it have a problem in reusing the cell.
If the cell is reused before the network call completion then it will assign the downloaded image to another cell.
So I changed the code like following.
var imageArray: [UIImage]?
let downloadQueue = dispatch_queue_create("com.donbytyqi.Papers", nil)
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell
if let oldImage: UIImage = imageArray[indexPath.row] {
cell.imageView.image = oldImage
return cell
} else {
cell.imageView.image = nil;
downloadImage(indexPath);
}
return cell
}
func downloadImage(indexPath: NSIndexPath) {
dispatch_async(downloadQueue) {
let imageURL = NSURL(string: "https://unsplash.it/200/300/?random")
let imageData = NSData(contentsOfURL: imageURL!)
var image: UIImage?
if imageData != nil {
image = UIImage(data: imageData!)
}
let cell = self.collectionView .cellForItemAtIndexPath(indexPath) as! ImageCollectionViewCell
dispatch_async(dispatch_get_main_queue()) {
cell.imageView.image = image
}
}
}
Hope this helps.
Let me explain what is going on actually.
When you scroll and go back you actually see the previously displayed cell with previously downloaded image (because of dequeueReusableCellWithReuseIdentifier:), and you will keep seeing that image until your new image will not downloaded, i.e. until execution of cell.imageView.image = image line.
So, you have to do following:
set cell.imageView.image = nil after dequeueReusableCellWithReuseIdentifier: line, like so:
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell
cell.imageView.image = nil;
//...
This will remove previously downloaded image from imageView until new image download.
You should use something like SDWebImage or UIImageView+AFNetworking for async image downloading with cache support, because every time that your method is called the images will be downloaded again and again instead of getting cached image, and that is waste of traffic.
Good luck!