UICollectionView cellForItemAt is called twice when scrolling - ios

I have a UICollectionView with the following settings:
Estimate Size = None
Content Insets = Never
Scroll Direction = Vertical
Paging Enabled = true
When I scroll to the second cell, the cellForItemAt method is triggered twice, with indexes 1 and 2 at the same time, and accordingly, when I swipe to the last cell, the cellForItemAt method does not work.
cellForItemAt does not work correctly only on swiping to the second cell, in all other cases it is OK (one index per swipe)
I also noticed that cellForItemAt is triggered not as soon as a new visible cell appears, but with a delay (about 100 pixels on top, the standard height is 568 pixels of cells)
Because of this problem, I do not work cellForItemAt on the last cell, also in the cells there are photos that are loaded by URL (I tried without loading images, the problem is the same), and they are in random cells, this is also a problem.
I do not know how to explain and solve this issue, thank you for any help
UP
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: TestCell.identifier, bundle: nil), forCellWithReuseIdentifier: TestCell.identifier)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("cellForItemAt: \(indexPath.item)")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TestCell.identifier, for: indexPath) as! TestCell
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
If I scroll to the second cell, the cellForItemAt works twice, and when the last cell, the cellForItemAt does not work at all. Accordingly, the configuration methods for the last cell do not work. I gave a simple example, but the problem is the same.
How do I load a cell on a user's scroll correctly?

As #PaulW11 said in his comment, your data source's collectionView(_:cellForItemAt:) method can be called at any time, with any indexPath. You should code your data source to provide any cell, for any valid indexPath, at any time, in any order.
The only limitation is that the collection view first calls your data source's numberOfSections(in:) and collectionView(_:numberOfItemsInSection:) methods, and won't ask for a cell for an indexPath that is out of the valid ranges indicated by those methods. Other than that, you should not make any assumptions about the cells the collection view will ask for, or the order in which it requests them.

Related

Swift UICollectionView Cell being rendered twice

I am writing a customer calendar as a challenge for me to practice UICollectionView. This issue has been puzzled me for few days. So the problem is when it reuses the cell, the load data source function has been called twice or three times, then it leads to:
Sometimes it's called three times and my calendar days become 100 110 etc.
Here is the delegate and datasource code:
extension CalendarViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.calendarPages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CalendarCell
cell.calendarPage = self.calendarPages[self.currentPageIndex]
cell.contentView.addSubview(self.buildCalendarPage(index: indexPath.row, frame: cell.contentView.frame)) // this is where it add new month page to the calendar, and it's randomly called multiple times
print("cell called")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
I know what's going one there, so basically I made it too complicated, there is a calendar UICollectionView in another UICollectionView, (I just want to make it scrollable), so each time when the cell is reused, I forget to reload the UI
Solution, keep only one UICollectionView, each cell is a day, and create another UICollectionViewCell class to control the reusable cell, thanks for the Babar's help

"Index Out Of Range" error in UICollectionView when dataSource array is correct

I'm using sizeForItemAt indexPath with a UICollectionView.
When I commit an action to remove an item from the collectionView and from the dataSource array, I call reloadData. However, in sizeForItem, I'm getting a Fatal error: Index out of range.
The dataSource count is correct, and is reflecting the change. For example, from 4 to 3 items. The indexPath that sizeForItem is trying to access is 0 - 3, the fourth index.
Why am I getting this error? Does it matter that sizeForItem runs before either cellForItem or numberOfItemsInSection?
After removing the item and calling reloadData, sizeForItem is called. Here are the delegate and dataSource methods:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return array.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath)
cell.updateStyle
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let view = array[indexPath.row]
return view.intrinsicContentSize
}
Please let me know if I can clarify.
Edit: Here's where I'm removing the item from the dataSource array
func deleteItem() {
removeItem(item)
collectionView.reloadData()
}
As #ozzieozumo said, the call to sizeForItemAt indexPath was getting called before reloadData.
After mutating the dataSource array, I changed the reloadData call to come first.

How to prevent dequeued collection view cells from overlapping each other?

I've been trying to set up a collection view where I have the user submit several strings which I toss in an array and call back through the collection view's cellForItemAt function. However, whenever I add a row to to the top of the collection view, it adds the cell label literally on top of the last cell label so they stack like this. Notice how every new word I add includes all the other previous words in the rendering.
The code I have at cellForItemAt is
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InterestsCell", for: indexPath) as? InterestsCell {
cell.interestLabel.text = array[indexPath.row]
return cell
} else {
return UICollectionViewCell()
}
}
and the code I have when the add button is pressed is
func addTapped() {
let interest = interestsField.text
array.insert(interest!, at: 0)
interestsField.text = ""
collectionView.reloadData()
}
I'm not sure what's going on. I looked everywhere and tried to use prepareForReuse() but it didn't seem to work. I later tried deleting cells by calling didSelect and the cells would not disappear again. Any help is appreciated!
This is the code I have in my custom collection view cell implementation in the event that this is causing the error
To do this paste these functions in your project
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
you can play around with the values :)
This can be easily implemented using UITableView. So try to use UITableView instead of UICollectionView.
It looks like you're adding a new label every time you set the text. If you want to lazily load the UILabel you need to add lazy
lazy var interestLabel: UILabel = {
...
}()
I wouldn't do it this way though, I would create a reference to the label
weak var interestLabel: UILabel!
Then add the label in your setupViews method
func setupViews() {
...
let interestLabel = UILabel()
...
contentView.addSubview(interestLabel)
self.interestLabel = interestLabel
}

When I scroll UICollectionView (paging enabled) too fast, it scrolls two pages

my qustion is simple.I have searched a lot and i'm familiar with UICollectionView but never saw this behavior of UICollectionView. please don't answer quickly and please read all my question.
I'm working on a project that uses collectionView (and auto layout). my collectionView is paging enabled and is working fine. but when I scroll it too fast (I mean very too fast) it scrolls two pages and shows the contents for next next row (previous previous row). also this code :
decelerationRate = UIScrollViewDecelerationRateFast
didn't work.
I created a temp project that have 1 controller and a simple collectionView (that is paging enabled and the cells's size are same as the viewController size). and there is just a label in the cell. the lable number is same as the collectionView row. also set this:
decelerationRate = UIScrollViewDecelerationRateFast
when I scroll it very fast from 1 to 2, it stops on 3 or vice versa.
this is my temp project code (that is auto layout):
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.sectionInset = UIEdgeInsets.zero
collectionViewLayout.minimumLineSpacing = 0
collectionViewLayout.minimumInteritemSpacing = 0
collectionViewLayout.scrollDirection = UICollectionViewScrollDirection.horizontal
self.collView.collectionViewLayout = collectionViewLayout
self.collView.decelerationRate = UIScrollViewDecelerationRateFast
}
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollCell
print("cell For Item At Row \(indexPath.row)")
cell.lblNumber.text = "\(indexPath.row)"
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: collectionView.frame.size.width, height: collectionView.frame.size.height)
}
anybody knows what should I do? is it a normal behavior for UICollectionView?

Autoresize UICollectionViewCell size inside UITableView

I am trying to create simple educational app about movies, and I need to create movie frames horizontal scroller.
I create collection view for that:
But if I build it for another target (iPhone 6s Plus for example), I have this:
How to solve this problem?
My scene controller code (Swift 3):
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.backgroundColor = UIColor.black
// Configure the cell
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
I think that you can try to implement the method of UICollectionView delegate:
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(yourHeight, self.view.frame.size.width)
}
in this way, your collectionView-cell inside in tableView will appear as big as the screen.
First I want to say something about UITableViewAutomaticDimension you use, in case you not exactly understand how it works.
Your expectations probably are, that UITableViewAutomaticDimension will infer tableView to make a cell with size of entire tableView, but it doesn't work this way.
UITableViewAutomaticDimension is just infers tableView, that inner size of your cell will be used as a returned here; so if you add some constraints, views with constraints, or whatever inside your tV, its size will be used.
and second - your comment doesn't make sense at all for me, because your posted code isn't related to cell of collectionView and I can't comment anything related to size of its cells
EDITED:
to make all cells with width of your screen, return correct size from delegate method of collectionViewFlowLayout - sizeForItem

Resources