Basic Information:
I have a tableView and each tableView cell containing an image and few labels.
I use tableView prefetch data method to make API calls to fetch data as the tableView scrolls.
Issue:
Sometimes I see a particular cell showing an image of some other cell.
This happens when I scroll a little faster than normal speed.
Note: In order to overcome from this issue I just need to scroll the tableView up/down
Things tried:
I have set the imageView to nil in prepare for reuse method.
The conclusion I have reached:
By debugging the issue I understood that once a cell is visible I make an image request using Alamofire Image but I scroll it before we received the response.
So, what might be happening is that on receiving a response it is setting an image for the cell but that cell is not visible as I am reusing the cells. The cell contains some other data.
Please let me know how can I cancel the request if the cell is not visible.
Let me know if I am missing something in the question.
What I do in this situation is set the cell's tag to match the row number.
When the image requests complete, I check if it still has the same tag (if it was reused, tag would be changed).
Something like this, in cellForRowAt indexPath:
cell.tag = indexPath.row
ImageService.shared.getImage(completion: { (image) in
if let image = image, cell.tag == indexPath.row {
//apply image
}
}
This doesn't cancel the request, but it ignores the result if the cell is not in the same position anymore.
This is happening because tableView reuse the cells and if it having a image downloading in queue and you scroll and the downloading is complete, it shows the downloaded image for few second until correct image download.
use placeholder in af_setImage method. it will solve your problem
imageView.af_setImage(withURL: URL(string: img)!, placeholderImage: UIImage(named: "product_placeholder")
Related
This questions is referencing just before a tableview fetches its data and displays its cells.
I've seen a few apps lately display a rough outline type image of the tableview cells for the brief moments before the populated cells get displayed.
How is this done?
Is a placeholder image used for the entire tableview or are placeholder type images rendered for each cell until the cell is dequeued?
Here are examples from Facebook and the fiverr app
Create a separate UITableViewCell class where the content of the cell is a UIImageView that has some kind of placeholder image of what your cells will look like. Populate the UITableView with those cells while your background request is being made. When the request completes, start a table update in which you remove all the placeholder cells, then insert all the "real" cells.
According to me it would be better to add backgroundView to tableView.
write:
while searching /fetching data:
if results.count == 0{
tableview.backroundView = emptyBlurView
}
once data is received so before reload :
tableview.backroundView = nil
when start requesting on server show the place holder cell and network response are received show the data container cell. using a placeholder cell same as activity indicator.
FaceBook are using simmer effect for its placeholder cell.
Pod
https://github.com/malkouz/ListPlaceholder
https://github.com/Juanpe/SkeletonView
During a recent interview, I was asked a scenario like #9 of these common interview questions regarding downloading images asynchronously into a table view cell. I understand the necessity for it to be called in cellForIndexPath and asynchronously but I was stumped as to how to check to see if the cell is still in view after the async call is complete (see the bullet #3 excerpt below). In other words, after an async call, how can I determine whether the table cell I was fetching data for is still in the view.
When the image has downloaded for a cell we need to check if that cell
is still in the view or whether it has been re-used by another piece
of data. If it’s been re-used then we should discard the image,
otherwise we need to switch back to the main thread to change the
image on the cell.
You should start downloading your image in the background with a callback mechanism that can decide if the image should still be displayed after it's been loaded.
One option would be to subclass UIImageView or UITableViewCell and store a reference to the NSURL of the image. Then, when your callback is called, you could check if the image view or the cell's cached URL is the one of the image you have, and decide to display it or not.
I wouldn't recommend on:
relying on a view's tag as it requires some sort of association table between a NSURL and an integer, which requires a manager object and is not helping reusability of your code
relying on the cell's indexPath as updates of the table or cells being reused for other index paths could occur while the network request happened
A more advanced options is described in Associated Objects, by NSHipster:
When extending the behavior of a built-in class, it may be necessary to keep track of additional state. This is the textbook use case for associated objects. For example, AFNetworking uses associated objects on its UIImageView category to store a request operation object, used to asynchronously fetch a remote image at a particular URL.
You can simply check whether the UITableViewCell is still in the view or not by using the following method of UITableView:
// return indexPaths that are visible
var indexPathsForVisibleRows: [IndexPath]?
Not to check whether to reload a specific row or not, you can do it by using the following method:
func downloadImageForCell(indexPath: IndexPath) {
// Asynchronous download method here
// After download is completed. Call the below in mainqueue
if let indexPaths:[IndexPath] = self.tableView.indexPathsForVisibleRows {
// the above line checks if indexPath is available
if indexPaths.contains(indexPath) {
self.tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.none)
}
}
}
Please let me know if you have any problems in implementing this code
Update: This Github link contains the project. If possible download and tap on Reload on top you'll get to know the difference. and Mr. Zhang thank you for "Aspect Scale to Fill" suggestion. it helped me little.
I've a tableview which loads images Asynchronously (with AFNetworking UIImageView Category Class).Images are compressing for first time on visible cells. Once I reload again or scroll op and down Images are adjusting according to its size.
Basically I'll get different size Images. Which width should be cell width, height can be anything
Implemented all necessary steps for selfsizing(proper autolayout constraints and proper delegate methods)
//my tableview setup
self.tblTest?.estimatedRowHeight = 1000.0 //i've tried with all possible guesses and
//tried with delegate also
self.tblTest?.rowHeight = UITableViewAutomaticDimension
I've added constraints as below:
in my test cell.
func setUpImage(url:NSURL){
//tried with another function also without NSURLRequest
self.imvTest?.setImageWithURLRequest(NSURLRequest(URL: url),
placeholderImage: UIImage(named: "store_placeholder"),
success: { (requset, response, image) -> Void in
self.imvTest?.image = image
print(image.size)
}, failure: { (error) -> Void in
self.imvTest?.image = nil
})
}
Result
Expected
How can i prevent image squeezing for the first time??
P.S: I don't think its correct to reload table view each and time image is being set.
Please suggest me what i've been missing and any content priorities etc.,??
Try calling
tableView.beginUpdates()
tableView.endUpdates()
after setting the image in the success closure
Or try tableView.reloadRowsAtIndexPaths with the indexPath of the cell after setting the image
What I believe is happening is that the cell gets laid out with its original content size. Then the new image asynchronously comes in and gets set. Typically you set the data for the cell in cellForRowAtIndexPath:, which occurs before cell layout. However when an image comes in asynchronously, layout has already happened, so it's too late.
When a new image comes in, the data has changed, so you need to tell the tableview to reload the data.
Instead of reloading the tableview as a whole however, you can reload an individual cell when the image comes in. This will of course be much more performant than reloading the entire tableview.
My app downloads the user's Facebook profile picture with an asynchronous (NSURLConnection) connection so that it can display the profile picture on a customized UITableViewCell. The problem is; the customized tableViewCell is created before the picture gets downloaded so the delay leads to an empty tableViewCell without the picture. How can I solve this problem?
My approach is to reach each cell by using a (for-in enumeration) and (a tag for each cell) in "connectionDidFinishLoading" method.
So guys, what do you think about my approach and do you have any better approaches???
Thanks for your help,
E.
Trying to tag and loop through your cells to find matches won't work because of UITableViewCell reuse. There are only as many cells in memory as you can see on screen, and these cells get recycled to display different data. Therefore, you won't be able to create a tag for each row in your table view, because the table view is only using a handful of cells.
What you should do instead is create a UITableViewCell subclass that knows how to asynchronously download and display in the image itself. Using the UIImageView category from AFNetworking is perfect for doing something like this, instead of having to manage the URL connection yourself. Import this category in your cell subclass, and write a method that calls setImageWithURL: to asynchronously download and display the image. Also, make sure to overload the UITableViewCell method prepareForReuse, in which you should call cancelImageRequestOperation on your image view. This is so the request to download the image is cancelled if the cell is reused before the download is complete.
I have a collection view. In the cellForRow method I set an object in my collectionViewCell. The setter for that object takes the object's image url and downloads the image asynchroneously using AFNetworking. (the setter is in the custom cell subclass )
However when I download new images and i reloadData, I have to scroll down for the proper images to load... And sometimes the images change cells or duplicate (but I know the object in that cell doesn't change because the object title is the expected one.
What is happening?
If you want the image to swap out without reloading the cell (or scrolling down) you will need to call:
[self.imageView setImageWithURL:<#some url#> placeholderImage:<#placeholder image same size as imageview#>];
AFNetworking takes care of caching images so you can be safe to call the same setter every time cellForRow is called (to avoid image reuse on different cells)
NB: Its important the placeholder image is the same size as the imageView - your image will be swapped out.
I think this could be mainly because of the cell being reused bu the collectionview. Try setting the image property of your custom cell to nil right after dequeueReusableCellWithReuseIdentifier: