Why are UIImage's partially loaded in the TableViewCells? - ios

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()
}

Related

Swift UITableView Cell changes image when scrolled

I have a image inside my tableview cells that turns black when the cell is selected as well as the default cell turning gray. Problem is when I scroll down the screen and scroll up again the images are turn back as though they are not selected being image "cellSelected". So, after scrolling selected cells should have the image "cellSelected" yet they have "cellNotSelected". How can I make sure my app remembers the selected cells images?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CustomerSelectsBusinessesCell
cell.selectedCell.image = UIImage(named: "cellSelected")
updateCount()
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! CustomerSelectsBusinessesCell
cell.selectedCell.image = UIImage(named: "cellNotSelected")
updateCount()
}
You need to save the change of state in your data model somewhere, and then edit your data source methods (specifically cellForRowAtindexPath) to show the cell in its new selected state.
This is true of any change you make to a cell. If the cell scrolls off-screen, it gets recycled and any customizations you've made to it's views will not longer be associated with that indexPath any more. So when the user takes action on a cell, you always need to record the information in your data model.

Load One Element Asynchronously in TableViewCell after Other Element Loaded in Swift

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

Image not changing on didSelectRowAt

I've used a collection view and one table view in my view and on didSelect function image is not changing.
Below is the code :
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell:list2TableCell = list2Table.dequeueReusableCell(withIdentifier: "Cell") as! list2TableCell
let image = UIImage(named: "fill-square")
cell.tickImage.image = image
}
Try:
if let cell:list2TableCell = list2Table.cellForRow(at: indexPath) as? list2TableCell{
let image = UIImage(named: "fill-square")
cell.tickImage.image = image
}
First of all, all you're doing is making a new cell that lives nowhere and changing its image — and then throwing away the cell. That's pretty silly! What you want to do is change the image of the cell that lives at the indexPath you were given.
Second, even doing that would be wrong. What you must do is change the image in your data model and then tell your table view to reloadData, so that cellForRowAt will be called and it will set the image in the cell.

Dynamic Table Image View in Swift

I have a very similar question as posted here (Dynamic UIImageView Size Within UITableView) where I'm trying to dynamically retrieve an image from Firebase and make the resulting tableview adjust to the height of the image given a fixed width across the screen based on the aspect ratio. All the articles I read says to make the cell calculation based on cellforRowAt, but my actual image is within the TableViewCell. Can someone please help?
Tableview controller:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedTableViewCell", for: indexPath) as! FeedTableViewCell
cell.configureCell(post: postArray[indexPath.row])
return cell
}
TableViewCell:
func configureCell(post: Post){
self.postImage.sd_setImage(with: URL(string: post.postImageURL),
placeholderImage: UIImage(named: "default"))
}
you have cell.setNeedsLayout() inside the completion handler of setting image method, like this:
cell. postImage.kf.setImage(with: URL) { _, _, _, _ in
cell.layoutIfNeeded()
}
First of all, you need to use Autolayout to calculate the proper cell height by creating a proper set of constraints that would determine the cell height based on content. I am going to assume you did that.
Then you have to tell the tableView you want it to use Autolayout to calculate height:
// my best estimation of the height
tableView.estimatedRowHeight = 144
// but the real height is calculated by autolayout
tableView.rowHeight = UITableViewAutomaticDimension
Now this would work if the autolayout could calculate the height correctly in cellForRowAt. But since the image is downloaded asynchronously, the image is set later, when the cell may be already presented. This requires you to provide a way in which a cell can tell the tableView that it has downloaded its content and its layout needs to be recalculated. To do so, use this method in the viewController with the tableView:
func recalculateTableViewLayout() {
self.tableView.beginUpdates()
self.tableView.setNeedsLayout()
self.tableView.endUpdates()
}
You will need to pass the reference to the viewController with the tableView to each cell (I recommend to use delegate pattern for that, here for brevity I will simply sketch it using it directly):
class FeedTableViewCell: UITableViewCell {
weak var feedViewController: FeedViewController?
// etc.
And in the cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedTableViewCell", for: indexPath) as! FeedTableViewCell
cell.feedViewController = self
cell.configureCell(post: postArray[indexPath.row])
return cell
}
Then use completion handler of sd_setImage to tell the tableView to recalculate its layout when the image gets downloaded:
func configureCell(post: Post){
self.postImage.sd_setImage(with: URL(string: post.postImageURL), placeholderImage: UIImage(named: "default")) { (image, error, cache, url) in
self.feedViewController?.recalculateTableViewLayout()
}
}

Transform from NSData to UIImage very slow, block UITableView

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.

Resources