I got NSData from Core Data, and when I transform from NSData to UIImage, it make my UITableView very slow to scroll. I already resize my Image before I store image to Core Data. Is there good way to do that. At first, I want to use Kingfisher, but it seems don't have this kind of method to achieve that.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "ListTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ListTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
//cell.imageContentPhoto.kf.setImage(with: url)
cell.imageContentPhoto.image = UIImage(data: photos[indexPath.row] as Data)//photo is NSData, I got it from Core Data
return cell
}
Your problem isn't that the transform is slow, but that it's done a lot of times when you scroll.
In order to optimize UITable, iOS does not create n cells for n rows. It creates x+2 cells (IIRC), where x is the number of visible cells.
When cellForRowAt is called, you call dequeueReusableCell, which takes one of the free cells and assigns it to the row. That way, when you scroll, there are no initializations of objects happening, and slowing the scrolling down.
The problem with your code (which is now evident), is that after the cell is allocated to the row, you again convert the image and initialize it.
What you should do is initialize the array of images in advance, for example in the viewDidLoad. Then your code would look like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "ListTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ListTableViewCell else {
fatalError("The dequeued cell is not an instance of MealTableViewCell.")
}
//cell.imageContentPhoto.kf.setImage(with: url)
cell.imageContentPhoto.image = imagesArray[indexPath.row] // or something similar
return cell
}
Of course, if you have a lot of different images, it might be worth doing some kind of lazy loading. Keep a place holder image while scrolling and when it stops, load the relevant images only. This is of course a lot more complicated and left as an exercise to the student.
Related
I have a table view with ten cells. Each of them contains an image view. When scrolling, sometimes images start jumping from one cell to another - I guess reuse does not work. I tried to reset the image to nil in prepareForReuse(), but the problem persists.
catImageView.image = nil
I also reset indexPath in cellForRowAt, but that didn't work either.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchCell.identifier, for: indexPath) as? SearchCell else { return UITableViewCell() }
let cat = cats[indexPath.row]
cell.setup(cat)
cell.catImageView.image = nil
return cell
}
In rxSwift for this I cleaned disposedBag. What can be done in this case?
I have a table view. First, I load text data for all cells from database. Next, I load image for every cell from another database. But when I tried to set images, they are not displayed.
If I use
let cell = myTableView.dataSource?.tableView(myTableView,
cellForRowAt: indexPath) as? MyTableViewCell
then images loaded only in invisible cells. It means that those cells that I see the very first (before scrolling down) don't show their images. But all others do.
Another way, if I use
let cell = myTableView.cellForRow(at: indexPath) as? MyTableViewCell
then the opposite happens - only those cells that I see show their images, but all others don't.
After that I do
cell.setImage(image: image)
func setImage(image: UIImage) {
guard let myImageView = myImageView else { return }
myImageView.image = image
}
I set images not in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {...} because as I said, images come after creating cells.
You need to refresh the Table View in the main thread after the load. You can do it with:
DispatchQueue.main.async {
self.yourTableView.reloadData()
}
I would like to load a tableview with a combination of synchronous and asynchronous (remote) data. The synchronous data loads immediately. How can I get the asynchronous data to load when it is ready? Do I put something in cellforRow or in viewwill appear?
Right now I set the label value in the tableview cell but the data is not updating
Here is my cellforRow code:
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
let row = indexPath.row
cell.textLabel?.text = Places[row].name
Utilities.shared.getWeather(query: Places[row].name as NSString) { (response1) in
print(response1)
DispatchQueue.main.async {
cell.detailTextLabel?.text = response1
}
}
return cell
}
The current behavior loads the data every scroll , and in worst case loads the same data many times at the same instant , so you need to keep it once by putting that logic inside your model to grab the required content and update it's content with it , then reload the table / indexPath an example is described Here
I have uitableview with 10 images in rows in my Xcode swift project. I use this code to show 10 images in rows in my uitableview:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(format: "Cell0", indexPath.row), for: indexPath)
var imageV = UIImageView()
imageV = cell.viewWithTag(5) as! UIImageView
imageV.image = UIImage(named:"image\(indexPath.row + 1).png")
But when I scroll my uitableview freezing very very very hard. Why is this happening? And How to fix it?
Create a custom cell
class MyCustomCell: UITableViewCell {
#IBOutlet weak var imgView: UIImageView!
}
In your storyboard, set the class of the cell to MyCustomCell and give it an identifier say cell.
Then, update your cellForRow method as below
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCustomCell
let imageName = "image\(indexPath.row + 1).png"
cell.imgView.image = UIImage(named: imageName)
return cell
}
UPDATE
Create a new swift file called MyCustomCell.swift
In storyboard, set your cell's class to MyCustomCell
Add a UIImageView in your cell
Connect the #IBOutlet
Set your cell's identifier
Update your cellForRow method
The tableview is lagging and freezing on the scrolling because of how CellForRow works. Essentially, the OS does not save cells that are displayed. For example, rows 1-2-3 are displayed on the screen and have gone through cellForRow. Rows 4 is about to be displayed and row 1 will be removed off the screen. Row 4 will go through cellForRow for it's information to be displayed, and once row 1 is off the screen it will be removed from memory. So if you then scroll back to row 1, it will go through cellForRow and reload the image. So in this case your tableview is constantly loading images from the bundle as you scroll up and down.
Your solution might be to preload all your images and have them in an array so that the OS doesn't have to spend time loading the images repeatedly. Another way to go about might be to create a custom cell and simply have the cell handle loading it's own image by providing it the image name, rather than have the tableview manage the loading of every single's cell's image repeatedly. This would also clean up your cellForRow.
For example: make a new tableViewCell called imageCell which has an imageName property that could be an String. Once the cell is made, you simply pass it the string of it's image in cellForRow, and when the imageCell is created, have it load it's own image through this string in it's awakeFromNib.
let cell = tableView.dequeueReusableCell(withIdentifier: String(format: "Cell0", indexPath.row), for: indexPath)
to
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as CustomCell
cell.imageView.image = UIImage(named: "yourimagename")
Don't change your reusable identifier everytime. This is causing freezing in your case. Moreover create custom cell for adding UIImage.
I know this is quite a hot topic answered in several places but no answer yet solved my problem. I'm working on an app with a main tableview with multiple cell types (table can be up to hundreds of cells), each one has a different potential height, depends on its content. I'm trying to rely more on dynamic cell heights, calculated by the system when drawing the cell but my scrolling is badly affected when i'm trying to scroll to the bottom of the table.
I understand the estimated height of a cell should be really close to what it is eventually, but there is no way to do that unless I manually calculate the size of each cell by summing up all of its texts, images, constraints and so on... That pretty much knocks the edge out of using dynamic cell heights, doesn't it?
The best solution i've found online is caching up the real cell heights on "cellWillDisplay", but it only works after all cells are presented at least once.
Thing is, when my app loads, it automatically scrolls to bottom without animation so "cellWillDisplay" isn't called for all cells above.
This is my estimatedHeightForRow snippet:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let object = self.fetchedResultsController?.object(at: indexPath) as? Object {
if let objectID = object.objectIDPermanentString {
if let savedHeight = self.rowsHeights[objectID] {
return savedHeight
}
}
}
return UITableViewAutomaticDimension
}
This is my cellForRow method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let object = self.fetchedResultsController?.object(at: indexPath)
let cell = tableView.dequeueReusableCell(withIdentifier: object.id, for: indexPath)
self.configureCell(cell: cell, object: object)
return cell
}