I have a tableView with dynamic cells. Each cell has got an imageView and a textLabel. I'm working with Alamofire & AlamofireImage by the way.
My problem is that the first cells (which you see if view did appear) don't show the images. But if i scroll down i see the images of the other cells. And if scroll up again the images of the first cells appear too.
First of all i download user data in the viewDidLoad method. After this is done I update the tableview with tableView.reloadData().
override func viewDidLoad() {
super.viewDidLoad()
MYRequest_getUsers(//...
,
success: {response -> Void in
self.users = response
dispatch_async(dispatch_get_main_queue(),{
self.tableView.reloadData()
})
},
failure: {NSError -> Void in
debugPrint(NSError)
})
}
TableView counts the users and reloads itself.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users?.count ?? 0
}
In tableView_cellForRowAtIndexPath method i fill the cells with user data.
let cell = tableView.dequeueReusableCellWithIdentifier(self.identifier) as! UserCell
cell.userNameLabel.tag = indexPath.row
let rowData: User = self.users?[cell.userNameLabel.tag] as User!
cell.userNameLabel.text = rowData.getName()
let avatarURL = rowData.getAvatarURL() ?? ""
cell.userImageView.af_setImageWithURL(imgURL, placeholderImage: placeholderImage)
return cell
My idea was that after user data was loaded, the tableView begin to draw itself and place the name (because it is already in userData). But the image is downloaded (i suppose) asynchronously, so that they come from the backend after some delay and tableView is already drawn. After scrolling up and down i see them.
I want to open the screen and without scrolling up and down. Tableview should set automatically the images after they were loaded. So i thought to make a callback and use instead of
cell.userImageView.af_setImageWithURL(imgURL, placeholderImage: placeholderImage)
this
Alamofire.request(.GET, avatarURL)
.responseImage { response in
cell.userImageView.image = response.2.value
dispatch_async(dispatch_get_main_queue(), { () -> Void in
tableView.setNeedsLayout()
})
}
But nothing changes... I've tried a lot and looked similar problems but can't solve it... I hope you can give me some hints or much better a solution! :)
Thanks in advance
I would like to give you a direction by posting the steps (best practice to follow in such case):
Step 1 : Fetch images in background as soon as you have image URLs available from server. I prefer doing this on operation queue where I download multiple images concurrently. You can write a helper class which takes a completion block and implement NSOperation/NSOperationQueue.
Step 2 : While images are being downloaded, for the eyes of users, put a loading overlay on image view. You can do this in cellForRowAtIndexPath:.
Step 3 : Once images are available, cache them in file system. This is to avoid any scrolling abrupt behaviour.
Step 4 : Once images are available, call the completion block.
Step 5 : In completion block, reload your table on main thread. Ensure your cellForRowAtIndexPath: first check the image in cache, if present use it, if not present show loading overlay if image fetch is on flight. If image is not available in cache and download is also not going on, show some default image.
This question is couple of years old, but maybe this answer can be useful to someone else with a similar problem, like me before to find the right solution.
Since the images are loaded asynchronously, we have to force a cell update when the download is finished if we didn't provide a fixed height for the UIIMageView. This because cell updating (i.e. AutoLayout Constraints recalculation) is done automatically only after cellForRowAt method, that is called when a cell is displayed for the first time, or when the table is scrolled to show other cells. In both cases probably the images are not yet downloaded by the af_setImage() method, so nothing but the placeholder will be displayed as their sizes are unknown for the moment.
To force a cell update we need to use beginUpdates() and endUpdates() methods, putting them inside the completion handler of .af_setImage(). This way, every time the downloading is completed, the cell will be updated.
But, to avoid a loop, before to call beginUpdates()/endUpdates() we have to check if we have already update the cell before, because by calling these methods, the cellForRowAt method is called again and consequently the af_setImage() and its completion closure with beginUpdates()/endUpdates() inside it).
This means that we have to update the cell only when the download is just finished, and not when the imaged is cashed (because, if it is cashed, it means that we have already updated the cell). This can be accomplished by checking the response of the completion handler: if it is not nil, the image was just downoladed, if it is nil, the image was cashed.
As a side benefit the cell height will be automagically adjusted (remember to put tableView.estimatedRowHeight = 400 and tableView.rowHeight = UITableViewAutomaticDimension in your viewDidLoad() method)
Finally, here it is the code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue your cell
let cell = self.tableView.dequeueReusableCell(withIdentifier: "YourCustomCellIdentifier") as! YourCustomTableViewCell
// get picture url from the data array
let pictureUrl = self.yourCellsData[indexPath.row].pictureUrl
// async download
cell.pictureView.af_setImage(
withURL: URL(string: pictureUrl)!,
placeholderImage: UIImage(named: "YourPlaceholder.png"),
filter: nil,
imageTransition: UIImageView.ImageTransition.crossDissolve(0.5),
runImageTransitionIfCached: false) {
// Completion closure
response in
if response.response != nil {
// Force the cell update
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
return cell
}
That's all folks! ;-)
Few things to achieve this.
call data download in viewwillappear and not in viewdidload.
in viewdidload, hide the tableview.
in viewdidAppear: check the data has downloaded and if so then call
[tableview reloaddata] method.
Related
I need to load 100 images in cells. If I use this method in tableView cellForRowAt:
cell.cardImageView.image = UIImage(named: "\(indexPath.row + 1).jpg")
and start scrolling fast my tableView freezes.
I use this method to load the image data in background that fix freezes:
func loadImageAsync(imageName: String, completion: #escaping (UIImage) -> ()) {
DispatchQueue.global(qos: .userInteractive).async {
guard let image = UIImage(named: imageName) else {return}
DispatchQueue.main.async {
completion(image)
}
}
}
in tableView cellForRowAt call this:
loadImageAsync(imageName: "\(indexPath.row + 1).jpg") { (image) in
cell.cardImageView.image = image
}
But I have one bug may arise in this approach, such that while scrolling fast I may see old images for a while. How to fix this bug?
Your cells are being reused.
When cell goes out of screen it gets to internal UITableViews reuse pool, and when you dequeueReusableCell(withIdentifier:for:) in tableView(_:cellForRowAt:) you get this cell again (see, "reusable" in name). It is important to understand UITableViewCell's life cycle. UITableView does not hold 100 living UITableViewCells for 100 rows, that would kill performance and leave apps without memory pretty soon.
Why do you see old images in your cells?
Again, cells are being reused, they keep their old state after reuse, you'll need to reset the image, they won't reset it by themselves. You can do that when you configure a new cell or detect when the cell is about to be reused.
As simple as:
cell.cardImageView.image = nil // reset image
loadImageAsync(imageName: "\(indexPath.row + 1).jpg") { (image) in
cell.cardImageView.image = image
}
The other way is detecting reuse and resetting. In your cell subclass:
override func prepareForReuse() {
super.prepareForReuse()
self.cardImageView.image = nil // reset
}
Why do you see wrong images in your cells? By the time completion closure sets image into cardImageView, UITableViewCell has been reused (maybe, even, more than once). To prevent this you could test if you're setting image in the same cell, for example, store image name with your cell, and then:
// naive approach
let imageName = "\(indexPath.row + 1).jpg"
cell.imageName = imageName
loadImageAsync(imageName: imageName) { (image) in
guard cell.imageName == imageName else { return }
cell.cardImageView.image = image
}
There is a lot of stuff to take care of when designing lists, I won't be going into much detail here. I'd suggest to try the above approach and investigate the web on how to handle performance issues with lists.
in your cell class you need to declare
override func prepareForReuse() {
super.prepareForReuse()
}
to prepare the cell for the reuse
I've just implemented the library Charts (https://github.com/danielgindi/Charts) in a tableview, but I experience pretty heavy lag during scrolling, when my charts hold a lot of data.
I have a method inside the ChartTableViewCell where I draw the chart based on the data I pass and call from my viewcontroller.
func updateCell(chartData: ChartData) {
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.readings = (readings != nil) ? readings!.readings : []
self.period = period
if (chartData.isKind(of: LineChartData.self)) {
data.lineData = chartData as! LineChartData
}
else if (chartData.isKind(of: BarChartData.self)) {
data.barData = chartData as! BarChartData
}
}
DispatchQueue.main.async {
self.chartView.data = data
}
}
In my tableViewController I call the function after parsing the data:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let meta = chartMetas[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "chartCell", for: indexPath) as! ChartTableViewCell
cell.readings = currentReadings
cell.updateCell()
return cell
}
What did I miss? Since the view is lagging so hard when scrolling.
UPDATE:
I tried, as suggested, to prepare the chart data in the viewcontroller and pass it to the cell. However it seems like the problem in the resuseable cells, the lags appears when I scroll and a new cell enters the screen. How can I fix this? It is pretty annoying.
UPDATE 2:
It looks like the Charts aren't supposed to be used in a tableview...
https://github.com/danielgindi/Charts/issues/3395
To get a serious performance boost, you'll likely need to implement UITableViewDataSourcePrefecting. By doing this, the table view will call into your delegate to let you know that a cell will be needed ahead of time. This will give your code a chance to prepare and render any data it needs.
From the documentation:
You use a prefetch data source object in conjunction with your table
view’s data source to begin loading data for cells before the
tableView(_:cellForRowAt:) data source method is called.The following
steps are required to add a prefetch data source to your table view:
Create the table view and its data source.
Create an object that adopts the UITableViewDataSourcePrefetching
protocol, and assign it to the prefetchDataSource property on the
table view.
Initiate asynchronous loading of the data required for the cells at
the specified index paths in your implementation of
tableView(_:prefetchRowsAt:).
Prepare the cell for display using the prefetched data in your
tableView(_:cellForRowAt:) data source method.
Cancel pending data load operations when the table view informs you
that the data is no longer required in the
tableView(_:cancelPrefetchingForRowsAt:) method.
You chart library is probably doing some pretty heavy processing when there are large data sets. If the author of this library didn't account for this, you are executing this heavy processing on the main thread every time you scroll and a new cell appears on the screen. This will certainly cause stuttering.
I would look to change the cell setup method to simply display a spinner or placeholder table, and then kick off the loading of data and building of the chart on a background thread. This should cause the UITableView scrolling to be smooth, but you will see the cells with placeholders as you scroll through the table, where the charts get populated after the background thread processing completes.
Running things on a background thread in Swift is pretty easy.
Just make sure when you are ready to update the UI with the chart, you do that on the main thread (Al UI updates should be executed on the main thread or you will see weird visual oddities or delays in the UI being updated). So maybe hide the placeholder image and show the chart using animations on the main thread after the heavy processing is done on the background thread.
What if you did something like this?
func updateCell(chartData: ChartData) {
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.readings = (readings != nil) ? readings!.readings : []
self.period = period
if (chartData.isKind(of: LineChartData.self)) {
data.lineData = chartData as! LineChartData
}
else if (chartData.isKind(of: BarChartData.self)) {
data.barData = chartData as! BarChartData
}
self.chartView.data = data
DispatchQueue.main.async {
// something that triggers redrawing of the chartView.
chartView.fitScreen()
}
}
}
Note that the main thread call is inside the background thread execution block. This means all the stuff will run on the background thread, and when that background thread stuff is done, it will call something in the chart library that will do the UI refresh. Again, this won't help if your chart library is written to force long running operations to happen on the main thread.
Try to prepare all the chart data before you pass it to the cell. I mean
make all the stuff you do I the updateCell() func in your tableViewController probably in viewDidLoad and pass the generated chart Datas to an array.
Then in tableView(tableView: , cellForRowAt indexPath: ) just pass the previously generated data to your cell.
So your updateCell func should looks like so:
func updateCell(lineChartData: LineChartData) {
// SET DATA AND RELOAD
chartView.data = lineChartData
chartView.fitScreen()
}
And you tableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let meta = chartMetas[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "chartCell", for: indexPath) as! ChartTableViewCell
cell.readings = currentReadings
cell.updateCell(lineChartData: arrayOfGeneratedChartData[indexPath.row])
return cell
}
Seems that you keeps entries in the cell, so don't do this. Keep them in the tableViewController. Pass only required data to the cell and don't do hard tasks in the cell methods.
Currently,
I have a feed for the user and
it uses uitableview and
there is a uicollectionview inside tableview for multi-images.
'Like' or 'Comment' functions are working well
but some issues happen when user taps 'Like'.
I do not want to show several changes,
but when user taps 'Like', I need to reload a cell and it shows another picture for a short time(bcs of reuse) and back to original image.
I tried to use the function, prepareForReuse().
However, I am not sure how to maintain the same image currently on the screen when they are reloading. Any idea u have?
For ur more information,
let me show my tableview's part of 'CellForItemAt' and collectionview's same method.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "storyCell", for: indexPath) as! StoryPhotoCollectionViewCell
let imageURL = photoList[indexPath.row]
let url = URL(string: imageURL)
DispatchQueue.main.async {
cell.imageView.kf.setImage(with: url, placeholder: #imageLiteral(resourceName: "imageplaceholder"), options: nil, progressBlock: nil, completionHandler: nil)
}
cell.imageView.kf.indicatorType = .activity
return cell
}
The collectionView's datasource is photoList array, so in the tableview, I have this code.
DispatchQueue.main.async {
cell.photoList = (story.imageArray?.components(separatedBy: ",").sorted())!
}
The issue should be due to delay in asynchronous download of the image,
I believe your code would be like this
cell.image = downloadFromURL(SOME_URL)
Add this single line code before assigning the image
cell.image = nil; // or some placeholder image
cell.image = downloadFromURL(SOME_URL)
If possible please provide more info
If I understand well your question, you need:
When the status change, you change the image so, this image can be stored as part of the app or external. If it is the same image, you need to keep it cached.
You start a timer like:
let countTimeBeforeChangeMyImageBack = DispatchTime.now() + .seconds(waitTimeBeforeStartCounting)
DispatchQueue.main.asyncAfter(deadline: deadlineTime, execute: {
self.YourMethodToChangeYourImageBack
})
At your method YourMethodToChangeYourImageBack you set the original image back.
Here, remember you could need to join back to the main thread before updating the UI:
// Re join the main queue after the callback
DispatchQueue.main.sync() {
// Your code to update the image
}
Hope it Helps.
I'm using AlamofireImage to set an image on a UIImageView in a UITableViewCell like so:
cell.imageView.af_setImageWithURL(url)
The image doesn't show after downloading. It will show the image the second time when it's loaded from the memory cache.
It seems that using a placeholder image makes all the difference.
This works and prints the address of the (downloaded) image:
cell.imageView.af_setImageWithURL(URL, placeholderImage: UIImage(named: "placeholder"), filter: nil, imageTransition: .None, completion: { (response) -> Void in
print("image: \(cell.imageView.image)")
})
This doesn't work and prints "image: nil"
cell.imageView.af_setImageWithURL(URL, placeholderImage: nil, filter: nil, imageTransition: .None, completion: { (response) -> Void in
print("image: \(self.image)")
})
It also works when setting the cells imageView to an empty image before doing af_setImageWithURL:
cell.imageView.image = UIImage()
Is this a bug in UITableViewCell, AlamofireImage or am I doing something wrong?
This question is couple of years old, but maybe this answer can be useful to someone else with a similar problem, like me before to find the right solution.
Since the images are loaded asynchronously, if we didn't provide a fixed height for the UIIMageView we have to force a cell update when the download is finished. This because cell updating (i.e. AutoLayout Constraints recalculation) is done automatically only after cellForRowAt method, that is called when a cell is displayed for the first time, or when the table is scrolled to show other cells. In both cases probably the images are not yet downloaded by the af_setImage() method, so nothing but the placeholder will be displayed since their sizes are unknown for the moment.
To force a cell update we need to use beginUpdates() and endUpdates() methods, putting them inside the completion handler of .af_setImage(). This way, every time the downloading is completed, the cell will be updated.
But, to avoid a loop, before to call beginUpdates()/endUpdates() we have to check if we have already update the cell before, because by calling these methods, the cellForRowAt method is called again and consequently the af_setImage() and its completion closure with beginUpdates()/endUpdates() inside it).
This means that we have to update the cell only when the download is just finished, and not when the image is already cashed (because, if it is cashed, it means that we have already updated the cell). This can be accomplished by checking the response of the completion handler: if it is not nil, the image was just downoladed, if it is nil, the image was cashed.
As a side benefit the cell height will be automagically adjusted (remember to put tableView.estimatedRowHeight = 400 and tableView.rowHeight = UITableViewAutomaticDimension in your viewDidLoad() method)
Finally, here it is the code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue your cell
let cell = self.tableView.dequeueReusableCell(withIdentifier: "YourCustomCellIdentifier") as! YourCustomTableViewCell
// get picture url from the data array
let pictureUrl = self.yourCellsData[indexPath.row].pictureUrl
// async download
cell.pictureView.af_setImage(
withURL: URL(string: pictureUrl)!,
placeholderImage: UIImage(named: "YourPlaceholder.png"),
filter: nil,
imageTransition: UIImageView.ImageTransition.crossDissolve(0.5),
runImageTransitionIfCached: false) {
// Completion closure
response in
// Check if the image isn't already cached
if response.response != nil {
// Force the cell update
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
return cell
}
That's all folks! ;-)
You can print the error by adding 2 line of code so that you will get more Idea about the issue.
cell.imageView.af_setImageWithURL(URL, placeholderImage: UIImage(named: "placeholder"), filter: nil, imageTransition: .None, completion: { (response) -> Void in
print("image: \(cell.imageView.image)")
print(response.result.value) //# UIImage
print(response.result.error) //# NSError
})
In my case there was an issue with Image URL. It was downloading the image when I open it in browser.
I have a tableview that I created with code (without storyboard):
class MSContentVerticalList: MSContent,UITableViewDelegate,UITableViewDataSource {
var tblView:UITableView!
var dataSource:[MSC_VCItem]=[]
init(Frame: CGRect,DataSource:[MSC_VCItem]) {
super.init(frame: Frame)
self.dataSource = DataSource
tblView = UITableView(frame: Frame, style: .Plain)
tblView.delegate = self
tblView.dataSource = self
self.addSubview(tblView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Subtitle, reuseIdentifier: nil)
let record = dataSource[indexPath.row]
cell.textLabel!.text = record.Title
cell.imageView!.downloadFrom(link: record.Icon, contentMode: UIViewContentMode.ScaleAspectFit)
cell.imageView!.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
print(cell.imageView!.frame)
cell.detailTextLabel!.text = record.SubTitle
return cell
}
}
and in other class I have an extension method for download images Async:
extension UIImageView
{
func downloadFrom(link link:String?, contentMode mode: UIViewContentMode)
{
contentMode = mode
if link == nil
{
self.image = UIImage(named: "default")
return
}
if let url = NSURL(string: link!)
{
print("\nstart download: \(url.lastPathComponent!)")
NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, _, error) -> Void in
guard let data = data where error == nil else {
print("\nerror on download \(error)")
return
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
print("\ndownload completed \(url.lastPathComponent!)")
self.image = UIImage(data: data)
}
}).resume()
}
else
{
self.image = UIImage(named: "default")
}
}
}
I used this function in other places and worked correctly, Based on my logs I understand that images downloaded without problem (when the cell is rendered) and after download of image, The cell UI not updated.
Also I tried to use caching library like Haneke but problem is exist and not change.
Please help me to understand mistakes
Thanks
After setting the image you should call self.layoutSubviews()
edit: corrected from setNeedsLayout to layoutSubviews
The issue is that the .subtitle rendition of UITableViewCell will layout the cell as soon as cellForRowAtIndexPath returns (overriding your attempt to set the frame of the image view). Thus, if you are asynchronously retrieving the image, the cell will be re-laid out as if there was no image to show (because you're not initializing the image view's image property to anything), and when you update the imageView asynchronously later, the cell will have already been laid out in a manner such that you won't be able to see the image you downloaded.
There are a couple of solutions here:
You can have the download update the image to default not only when there is no URL, but also when there is a URL (so you'll first set it to the default image, and later update the image to the one that you downloaded from the network):
extension UIImageView {
func download(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFill, placeholder: UIImage? = nil) {
contentMode = mode
image = placeholder
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, let response = response as? HTTPURLResponse, error == nil else {
print("error on download \(error ?? URLError(.badServerResponse))")
return
}
guard 200 ..< 300 ~= response.statusCode else {
print("statusCode != 2xx; \(response.statusCode)")
return
}
guard let image = UIImage(data: data) else {
print("not valid image")
return
}
DispatchQueue.main.async {
print("download completed \(url.lastPathComponent)")
self.image = image
}
}.resume()
}
}
This ensures that the cell will be laid out for the presence of an image, regardless, and thus the asynchronous updating of the image view will work (sort of: see below).
Rather than using the dynamically laid out .subtitle rendition of UITableViewCell, you can also create your own cell prototype which is laid out appropriately with a fixed size for the image view. That way, if there is no image immediately available, it won't reformat the cell as if there was no image available. This gives you complete control over the formatting of the cell using autolayout.
You can also define your downloadFrom method to take an additional third parameter, a closure that you'll call when the download is done. Then you can do a reloadRowsAtIndexPaths inside that closure. This assumes, though, that you fix this code to cache downloaded images (in a NSCache for example), so that you can check to see if you have a cached image before downloading again.
Having said that, as I alluded to above, there are some problems with this basic pattern:
If you scroll down and then scroll back up, you are going to re-retrieve the image from the network. You really want to cache the previously downloaded images before retrieving them again.
Ideally, your server's response headers are configured properly so that the built in NSURLCache will take care of this for you, but you'd have to test that. Alternatively, you might cache the images yourself in your own NSCache.
If you scroll down quickly to, say, the 100th row, you really don't want the visible cells backlogged behind image requests for the first 99 rows that are no longer visible. You really want to cancel requests for cells that scroll off screen. (Or use dequeueCellForRowAtIndexPath, where you re-use cells, and then you can write code to cancel the previous request.)
As mentioned above, you really want to do dequeueCellForRowAtIndexPath so that you don't have to unnecessarily instantiate UITableViewCell objects. You should be reusing them.
Personally, I might suggest that you (a) use dequeueCellForRowAtIndexPath, and then (b) marry this with one of the well established UIImageViewCell categories such as AlamofireImage, SDWebImage, DFImageManager or Kingfisher. To do the necessary caching and cancelation of prior requests is a non-trivial exercise, and using one of those UIImageView extensions will simplify your life. And if you're determined to do this yourself, you might want to still look at some of the code for those extensions, so you can pick-up ideas on how to do this properly.
--
For example, using AlamofireImage, you can:
Define a custom table view cell subclass:
class CustomCell : UITableViewCell {
#IBOutlet weak var customImageView: UIImageView!
#IBOutlet weak var customTitleLabel: UILabel!
#IBOutlet weak var customSubtitleLabel: UILabel!
}
Add a cell prototype to your table view storyboard, specifying (a) a base class of CustomCell; (b) a storyboard id of CustomCell; (c) add image view and two labels to your cell prototype, hooking up the #IBOutlets to your CustomCell subclass; and (d) add whatever constraints necessary to define the placement/size of the image view and two labels.
You can use autolayout constraints to define dimensions of the image view
Your cellForRowAtIndexPath, can then do something like:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
let record = dataSource[indexPath.row]
cell.customTitleLabel.text = record.title
cell.customSubtitleLabel.text = record.subtitle
if let url = record.url {
cell.customImageView.af.setImage(withURL: url)
}
return cell
}
With that, you enjoy not only basic asynchronous image updating, but also image caching, prioritization of visible images because we're reusing dequeued cell, it's more efficient, etc. And by using a cell prototype with constraints and your custom table view cell subclass, everything is laid out correctly, saving you from manually adjusting the frame in code.
The process is largely the same regardless of which of these UIImageView extensions you use, but the goal is to get you out of the weeds of writing the extension yourself.
oh my god, the layoutSubviews is not recommended to use directly
the right way to solve the problem is call:
[self setNeedsLayout];
[self layoutIfNeeded];
here, the two way have to call together.
try this, have a good luck.
Create your own cell by subclassing UITableViewCell. The style .Subtitle, which you are using, has no image view, even if the property is available. Only the style UITableViewCellStyleDefault has an image view.
Prefer SDWebImages library here is the link
it will download image async and cache the image also
and very easy to integrate into the project as well