I have a Chat Log which is simply a UICollectionView. Every sell has an avatar (UIImage) and a text bubble. I'm trying to fill avatars with proper images by fetching avatars URL from the server with Alamofire like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "chatMessage", for: indexPath) as! ChatMessageCell
let imageURL = URL(string: messagesArray![indexPath.item].userAvatar)
Alamofire.download(imageURL!).responseData { response in
if let data = response.result.value {
cell.userAvatar.image = UIImage(data: data)
}
}
cell.messageText.text = messagesArray![indexPath.item].userText
}
My problem: In some cells avatar appears in some is not. I think it's related to Alamofire async work. So my question is how to fill images to UICollectionView properly to show each avatar in each cell?
I think it's better to use SDWebImage , as according to your current implementation image fetching happens multiple times
imageView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
here SDWebImage
UICollectionView reuses existing cells. If you have a collectionView with one million cells there are not going to be one million UICollectionView cells instantiated but they got reused when they scroll out. Now your result callback blocks may set the image of the same cells.
To solve your problem you need to cache the images. There are multiple tutorials about this topic.
Related
I have a CollectionViewController in which I contact an API to download images based on a set of coordinates. I call this code in the cellForItemAt function at which time it updates the cell's images in realtime with images from Flickr. This works fine.
However, when scrolling up or down, it recalls this code and updates the cells again, when I'd prefer that it look at the existing cells, identify if they have been filled, and simply not run this code.
I have tried implementing logic before the networking code that checks to see if the imageView.images already exist in a local struct I assign them to, but that doesn't seem to work correctly.
Is there a simple method to tell cellForItemAt "for cells where you already have images, don't look for more"?
Here is my current code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath as IndexPath) as! CollectionViewCell
// Get images = using the URL
FlickrClient.sharedInstance().getImagesFromFlickr(latitude: selectedPin.lat, longitude: selectedPin.lon, page: pageCount) { (pin, error) in
if let pin = pin {
let url = pin.images[indexPath.item].imageURL
let data = try? Data(contentsOf: url)
performUIUpdatesOnMain {
cell.imageView.image = UIImage(data: data!)
cell.imageView.contentMode = .scaleAspectFill
}
}
}
return cell
}
Use SDwebImage libray for loading images from url.
https://github.com/rs/SDWebImage
Call Something like this on cell for row :
let url = pin.images[indexPath.item].imageURL
cell.imageView.sd_setImage(with: url, placeholderImage: UIImage(named: "placeholder.png"))
I'm writing a Swift app that displays photos fetched from server on each cell of my UITableViewController.
So far my code looks as follows:
func tableView(detailsPanel: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = detailsPanel.dequeueReusableCellWithIdentifier("cell") as! DetailsCell
let test:SingleTest = self.items[indexPath.row] as! SingleTest
if(test.photo != "") {
cell.myPhoto.af_setImageWithURL(NSURL(string: test.photo)!)
}
}
Now, the problem is that not every cell has a photo stored in cell.photo. Some of them are empty strings (""). In that situation when I quickly scroll through the table view, I see that those empty UIImageViews are filled with photos from other cells.
The quick fix for that seems to be adding an else block:
if(test.photo != "") {
cell.myPhoto.af_setImageWithURL(NSURL(string: test.photo)!)
} //this one below:
else {
cell.myPhoto.image = UIImage(named: "placeholderImg")
}
Now whenever there is no photo, the placeHolderImg will be displayed there. But... is there a way of avoiding it and just do not display anything there? And by not displaying anything I mean not displaying images from different cells?
You are reusing your cells, thus the cell will still use the previous image that it has loaded unless you set it to nil or a placeholder image. However I believe you do not need to use an if statement. You can use the placeholderImage parameter of af_setImageWithURL.
cell.myPhoto.af_setImageWithURL(NSURL(string: test.photo)!, placeholderImage: image)
I am stuck with a problem. I want to populate a tableview with some text and a profile image that can change i use this function for it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.hnk_setImageFromURL(NSURL(string: self.userCollection[indexPath.row].profile_picture)!)
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
nothing to suprising here. But when i change my own profile picture then i send it to the API and revist this function it shows the cached image. So i tought i might try something a bit diffent why not get all the images for every cell using Alamofire.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.image = UIImage()
//getting the cell image
Alamofire.request(.GET, self.userCollection[indexPath.row].profile_picture)
.response {(request, response, avatarData, error) in
let img = UIImage(data: avatarData!)
cell.profileImage.image = img
}
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
this works to a point where user scrolles very fast the image of a different user will be shown until the request gets fulfilled. Okey so make that happen somewere else and use an array for the images. I also tried that. but because its async the images would go into the array in the wrong order. So back to HanekeSwift. I read the documentation and saw i had a cache on disk but i could not clear or delete it.
to clear the cache i also tried:
NSURLCache.sharedURLCache().removeAllCachedResponses()
but i did not do a thing. it works in the Alamofire situation but its not a good solution either.
I want to use hanekeSwift because HanekeSwift is fast enough to get all the images. but i want to clear the cache everytime the contoller loads.
Any suggestions would be appreciated!
Cees
I found a the problem.
First i was running an older version of the pod. So after updating i could use the function.
Shared.imageCache.removeAll()
after you import haneke into the controller.
the final pice of code looked like this.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CommonCellView!
cell = self.tableView.dequeueReusableCellWithIdentifier("CommonCellView") as! CommonCellView
cell.nameLabel.text = self.userCollection[indexPath.row].display_name
cell.companyLabel.text = self.userCollection[indexPath.row].user_organisation
cell.profileImage.image = UIImage()
//getting the cell image
cell.profileImage.hnk_setImageFromURL(NSURL(string: self.userCollection[indexPath.row].profile_picture)!)
//deleting it from cache
Shared.imageCache.removeAll()
self.makeImageViewCircular(cell.profileImage.layer, cornerRadius: cell.profileImage.frame.height)
cell.profileImage.clipsToBounds = true
return cell
}
it works now but removing the cache all the time an other cell gets filled seems a bit overkill.
so i'm kind of a noobe to iOS and Swift also (actually only one month and a half). So I'm trying to make a tableview displaying some images coming from the web, I managed to get the correct info from a web API and display them in the tableview. But, the tableview with online images seems to freeze, I should wait 2-3 seconds for it to move just a little bit and freeze again, which is very frustrating.
I know it has something to do with synchronize, I also tried dispatch_async(dispatch_get_main_queue()){}, and since I'm developing with Swift not ObJ, I tried an open source Web Image download helper from Github called Kingfisher, but it didn't solve my problem.
So, please, help me, and thanks.
To keep it simple, here's the demo code:
var arrays = [
"NPWR02174_00_013F09F4AE3D70230FA695261A8E994D275DF62333/C878A05EF327D7D42E2AF5E1EE15672A1509962C.PNG",
"NPWR04270_00_01C53CAC0BACAC6F052AE4B968370CF9B899DDA81E/C1E1A8AD62EDE2FB95C3BD562F2783F157A0D011.PNG",
"NPWR04472_00_01CB90FF28A2EBB55D210932F56935B377E7A28ECB/DCA2F66365777A7330F685CD8CD5F90F2FE62671.PNG",
"NPWR04841_00_017B982A2A44BB607DECD77484C4670CCB505B65E4/91824E16D16C2789CC34A2F5F5444CD30D71C8F1.PNG",
"NPWR05212_00_018ADD99CEA95C240EFDC92FB993F6BC8C0AF7904D/7266E29159EE69D31AA24FB94BC0E90F5306774A.PNG",
"NPWR05254_00_015EB28BB38A5580D6A73CA0BDFB2F8C6864F85F66/76ED779ECE98E63E8A1A229AE626F62E8325D703.PNG",
"NPWR05257_00_01CB48AFF2A9F0795B5569E1DABBC612E3FA13B79A/3524288D6B1E5B4F51CF7DEA4A3E621913A1BA8C.PNG",
"NPWR05326_00_01DF67B968910B06FDABD8A2C2C6AAB72E0DD47048/8AB6EC3EC992C35472F02DD0C8D4122302C802E1.PNG",
"NPWR05401_00_0188285B6025E65909B39E295D2542DF589EDCCC38/620822A98E3CFC1CFB3B5EDCF3F00B19A5B328C2.PNG",
]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrays.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableview.dequeueReusableCellWithIdentifier("gameCell")!
dispatch_async(dispatch_get_main_queue()){
let image = cell.viewWithTag(101) as! UIImageView
let imageString = arrays[indexPath.row] as! String
let imageURL = NSURL(string: "https://trophy01.np.community.playstation.net/trophy/np/"+imageString)!
let data = NSData(contentsOfURL: imageURL)!
image.image = UIImage(data: data)}
return cell
}
It is because you are downloading the image synchronously. This is the code line that does so.
let data = NSData(contentsOfURL: imageURL)
Try downloading the image asynchronously and then update the cell once image is downloaded. Checkout this answer How to async load images on dynamic UITableView?
I have a collection view and array with URLs of different images. and when i launch the app, collection view starts to load images through the array:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CellView
var url = arr[indexPath.row]
var urls = NSURL(string: url)
var data = NSData(contentsOfURL: urls!)
cell.ImageView.image = UIImage(data: data!)
return cell
}
and the trouble appearse:
for example on 4th cell collection view loading all 4 urls for all 4 cells and it takest alot time. how can collection view load particular url for particular cell and don't spend time to load urls to cells that already loaded?
Thanks for any help!!
I suggest using a third party library for this matter, it called SDWebImage.
And than for each image view inside a cell set:
self.imageView.sd_setImageWithURL(url, completed: block)
Or you can use similar third party library, as Asaf, told you. I used to use HANEKE for DL/cache images.
Take a look: https://github.com/Haneke/HanekeSwift
:)