UICollectionViewDiffableDataSource cellProvider called more often than expected - swift

I'm using the UICollectionViewDiffableDataSource to populate my UICollectionView. After receiving a list of items via REST API, I create a new snapshot and apply it like this:
DispatchQueue.main.async {
var snapshot = NSDiffableDataSourceSnapshot<RegionSection, DiffableModel>()
snapshot.appendSections(RegionSection.allCases)
snapshot.appendItems(self.spotlights, toSection: .Spotlights)
snapshot.appendItems(self.vendors, toSection: .Vendors)
self.dataSource?.apply(snapshot, animatingDifferences: animated)
}
When setting up my cells in the cellProvider, I asynchronously load images from a URL. I noticed, that the first cell would frantically flick through all the images that are loaded and end up displaying a different image than it was supposed to. (For example the image intended to be displayed by the last cell).
I decided to investigate and figured out that the cellProvider closure is called twice as many times as expected. Also the collectionView.dequeueReusableCell function behaves weirdly for the first half of the calls as it returns the same cell each time even though there are no cells in the collectionView that could be dequeued.
My cellProvider closure:
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, entry) -> UICollectionViewCell? in
if let spotlight = entry as? Spotlight{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "spotlightCell", for: indexPath) as! SpotlightCell
cell.nameLabel.text = spotlight.title
cell.subtitleLabel.text = spotlight.subtitle
cell.categoryLabel.text = spotlight.type.getDescription().uppercased()
cell.imageView.loadImage(fromUrl: spotlight.titlePictureUrl)
return cell
}else if let vendor = entry as? Vendor{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "vendorCell", for: indexPath) as! VendorCell
cell.nameLabel.text = vendor.title
cell.assortmentLabel.text = vendor.assortmentDescription
cell.imageView.loadImage(fromUrl: vendor.titlePictureUrl ?? vendor.pictureUrls?.first ?? "")
if let distance = vendor.distance{
cell.distanceLabel.text = (distance/1000) < 1 ? (distance.getReadableString(withDecimalSeparator: ",", andDecimalCount: 0) + "m entfernt") : ((distance/1000).getReadableString(withDecimalSeparator: ",", andDecimalCount: 0) + "km entfernt")
}
return cell
}
return nil
}
Here is an example:
I create a snapshot containing 4 vendor entries (For simplicity I didn't add anything in the other section for this example)
The cellProvider is called 4 times (for each indexPath and entry) and the cell that is dequeued is the same one each time.
The cellProvider is called another 4 times (again, for each indexPath and entry) and this time the cells are different each time.
For each time the cellProvider is invoked I call loadImage, which tries to find an image for the URL in my image cache and if it cannot found one loads it asynchronously.
Since all calls happen almost simultaneously every image is loaded twice and the first cell displays one image after another until the last of the 4 URLSessions it initiated returns.
I can't imagine it is expected behaviour for the dataSource to call it's cellProvider closure that often and I simply can't figure out why this happens or find anything in the documentation on this.
I hope someone can explain to me why this happens and in case this is expected behaviour how to properly set up cells with asynchronous image loading using a DiffableDataSource.
EDIT:
The solution that worked for me was to use absolute instead of estimated sizes for my cells, as suggested by #Norb Braun!

Setting the estimated size to none fixed this issue for me. This solution may not work for you when you are required to use self sizing cells but if your cells keep the same size regardless the content you could give it a try.

Related

IndexPath.row of CollectionView gets changed while scrolling

I am new in swift, the issue I am facing is I have a collection view of height 2000. when I scroll down to the bottom, the indexPath.row gets changed due to which the logic gets disturbed. I understood that this happens due to the reusability of cells but how should I stop this. This is how I am initializing cellForItemAt.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "analysis", for: indexPath) as? analysisCollectionViewCell
if goalData.notesArr.count == 0 {
cell?.viewNotes.isHidden = true
cell?.constraintHeightNotesView.constant = 0
self.constraintHeightMainView.constant = self.constraintHeightMainView.constant - 400
}else{
cell?.viewNotes.isHidden = false
cell?.constraintHeightNotesView.constant = 470
}
I am changing the height of a few components(cell?.constraintHeightNotesView.constant) as well as of my mainView also(self.constraintHeightMainView.constant).
Please, edit the question so that We can see your code in full
This cell initializing method is correct
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "analysis", for: indexPath) as? analysisCollectionViewCell
But I can't understand why you need an collection view height 2000?
If you want to setup layout the collection view in full screen, You need to set constraints of collection view to the edges of the main view of your ViewController
if you collection view have a many elements, you will be able to scroll it.
This functionality is available without additional configuration.

So im looking to locate the indexPath.row of cell that has just been edited, aka inside of func textViewDidEndEditing [duplicate]

This question already has answers here:
Get section number in custom cell button action
(6 answers)
Closed 4 years ago.
Update: alright so I've tried to use the code as suggested in the duplicate question and does not seem to work for me, as Ive made clear previously I want to access the indexPath.row outside of the cellForRowAt function, but CANT access it through the custom cell, as I need the UITextView delegate in the tableViewController in order to update tableView on textViewDidChange in order to have the automatic resizing of cells to work.
ive tried to write this code and I've written notes as to what happens at each individual spot when I check it with console. ill post the code beneath.
as previously mentioned I want to save the text from user input, and erasing it once indexPath.row = row of text input.
//if endEditing, save text to array cellNumber, at the point inside the array, equalavant of the indexPath.row
func textViewDidEndEditing(_ textView: UITextView){
//get position of current row
var position: CGPoint = self.tableView.convert(CGPoint.zero, to: self.tableView) // always returns 0.0 0.0 pos
if let indexPath = self.tableView.indexPathForRow(at: position){ //returns 0, 0 always
let cellIndexPathRow = indexPath.row // problem: always comes back as 0, should return indexPath.row
print(cellIndexPathRow)
cellNumber.insert(textView.text, at: cellIndexPathRow)}
}
You can try to access the cell like this
func textViewDidEndEditing(_ textView: UITextView){
if let cells = self.tableView.visibleCells as? [CustomCellClass], let cell = cells.filter({$0.textView == textView}).first {
// Do what you want with the cell here
}
}

Condition that should change configuration of a cell is not working, probably because of how the feed is populated

I am using XCode 9, and I have created a collection view programmatically. I have a function called populateFeedCollectionViewCell, where I use the data I have gotten from the server to populate each cell of my collection view. I call this fuction at cell for item.
In my populateFeedCollectionViewCell function I check if the Item has an image of not, and when it doesn't have an image, I tell the image view to be frame 1,1, by altering its constraints.
For some reason, this isn't working properly. All the items that should be altered are being altered with success, but other items are being altered as well (just some of them).
I make the xcode print me the item when it enters the condition where I alter the constraints, and the weird thing is that the items arent being printed (therefore aren't entering the condition by mistake), but are changing its constraints.
My guess is that when the override cell for item is being called, it is mistaking the index or something. But I don't know how to be sure that that is the problem nor how to fix it.
Here is my code:
Where I call populateFeedCollectionViewCell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomFeedCell
presenter.populateFeedCollectionViewCell(cell: cell, item: itemArray[indexPath.row])
cell.shareButton.tag = indexPath.row
cell.delateButton.tag = indexPath.row
return cell
}
Where I give the condition, inside populateFeedCollectionViewCell:
if item.mediaUrl == "empty"{
debugPrint("entrou em empty", "media url:", item.mediaUrl, "title:", item.title)
cell.feedImageView.heightAnchor.constraint(equalToConstant: 206.0).isActive = false
cell.feedImageView.widthAnchor.constraint(equalToConstant: 341.0).isActive = false
cell.feedImageView.widthAnchor.constraint(equalToConstant: 1).isActive = true
cell.feedImageView.heightAnchor.constraint(equalToConstant: 1).isActive = true
} else {
cell.feedImageView.widthAnchor.constraint(equalToConstant: 1).isActive = false
cell.feedImageView.heightAnchor.constraint(equalToConstant: 1).isActive = false
cell.feedImageView.heightAnchor.constraint(equalToConstant: 206.0).isActive = true
cell.feedImageView.widthAnchor.constraint(equalToConstant: 341.0).isActive = true
}
The result on my feed:
ps: notice that when it doesnt have an image it is correct, but when you see the image as a line on top of the text, the constraints were applied when they shouldnt have been. Also, when I scroll the feed, other items change their constraints to the wrong way.
Image 1 (constraints applied correctly)
Image 2 (constraints applied when shouldn't be)
Image 3 (constraints not when shouldn't be)
Since there is cell resuing inside cellForItemAt , every time the cell reused it creates other constraints which conflicts , then cause unexpected results , you need to clear all constraints of feedImageView before creating new ones
Or better to create the constraints inside xib/custom class , then hook them as outlets and play with there constants
if item.mediaUrl == "empty"{
cell.feedImageViewWidth.constant = 1.0
cell.feedImageViewheight.constant = 1.0
} else {
cell.feedImageViewWidth.constant = 341.0
cell.feedImageViewheight.constant = 206.0
}
where
var feedImageViewWidth:NSLayoutConstraint! // or #IBOulet weak var .....
var feedImageViewheight:NSLayoutConstraint!
Warning:
Don't think constraints with
.isActive = false
deactivates old ones , No they add another deactivated constraint

Move UICollectionViewCell that is currently not loaded

I'm using Firebase to create a networking app for a client.
For every user, I add an observer to its Firebase property timestamp, that means that when the user uploads a status update, the Firebase observer gets triggered so that the users cell in the feed move to the top (every user only has one cell). Here's the Firebase code:
Database.database().reference().child(forUser.userID).child("timestamp").observe(.value) { (snapshot) in
if let timestamp = snapshot.value as? Int {
if let cell = self.feedCollectionView.cellForItem(at: indexPath) as? FeedCell {
self.feedCollectionView.moveItem(at: indexPath, to: IndexPath(row: 0, section: 0))
}
}
}
It is though very well possible that I have 100-200 users and maybe the last of them updates his status and has to be moved to the top, even though his collection view cell has never even been loaded.
When I run this, it prints for every visible cell that it is movable and for every invisible cell that it can't access the unloaded cell.
How can I access a cell that has never been loaded and move it to the top?
Instead of moving a cell that may or may not be loaded, you should update the collectionview's datasource by sorting it on timestamp. Then you can call self.feedCollectionView.reloadData() and it'll place it on the top automatically.

Swift - read above cell value in tableview

Basically I am trying to get a value from a custom table view cell, as I have made 5 prototype cells - each with a different tableViewCell class. However, what I am trying to do is to read the data from the cell above. I have called my classes: TableViewCell1, TableViewCell2 etc... So on the cellForRowAtIndexPath, I have the code like this:
if indexPath.row == 1 {
let cell2 = tableView.dequeueReusableCellWithIdentifier("secondCell", forIndexPath: indexPath) as! TierCell2
return cell2
}
This works, but I have also tried to do it like this, to be able to access the value above (with no luck) - which only results in an optional value error.
let cell1:TableViewCell1 = TableViewCell1()
cell2.PriceText.text = cell1.PriceText.text
Any ideas on how I might accomplish this?
I suggest you then try creating one custom table view cell with the 5 price tier labels that you want to display. Then in the cellForRowAtIndexPath you just deque a cell as the custom class and fill in all the tier information that you already have in your dictionary and return that cell.