Swift UICollectionView Cell being rendered twice - ios

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

Related

UICollectionView cellForItemAt is called twice when scrolling

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.

Which item was selected in a UICollectionView, if there 're multiple UICollectionViews

I have more than one collectionView in a ViewController. The cell of those collectionViews has the same format.. so I'm reusing them. So my question is: How to identify in the method
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath)
I don't want to do a couple of if's
I've found this solution everywhere, but really don't like it. Here is the code
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
if let aCell = cell as? ItemCollectionViewCell{
aCell.setupCell(with: self.items[indexPath.item])
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.colletionViewTwo{
// goto viewController1
}else if collectionView == self.colletionViewOne{
// goto viewController2
}
}
Create two classes that implement the collection view delegate and data source and use one of each. So you'll have these two extra objects in your current view controller.
Seeing your code now, the above is probably too heavy. Alternatively, add a dictionary in which you store the collection view as key and a selector as value. This is extensible as you say you want.
To be honest, what's your issue an if (or switch) statement like you have now?

"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.

Creating a horizontal scrolling menu

I am trying to create a menu where top horizontal scrolling view contains the menu categories. When I tap on one of the menu categories, ads related to the category appears.
When First is selected
--**First**---|--Second--|---Third--|--Fourth--| (scrolling categories)
------------------------------------------------------------------
\\\FirstA\\\ \\\FirstB\\\ \\\FirstC\\\ \\\FirstD\\\
\\\FirstA\\\ \\\FirstB\\\ \\\FirstC\\\ \\\FirstD\\\
------------------------------------------------------------------
When Second is selected
--First---|--**Second**--|---Third--|--Fourth--| (scrolling categories)
------------------------------------------------------------------
\\\SecondA\\\ \\\SecondB\\\ \\\SecondC\\\ \\\SecondD\\\
\\\SecondA\\\ \\\SecondB\\\ \\\SecondC\\\ \\\SecondD\\\
------------------------------------------------------------------
When I tap on First Category, data related to first comes as a collectionView. Similarly, when I tap on second Category, data related to second comes in the UICollectionView.
Now I am thinking of doing it in a UITableViewCell with two UICollectionViews. First UICollectionView will contain categories and second UICollectionView will contain data related to categories.
But I have never used two UICollectionViews in a single UITableViewCell.
So I am asking is it the correct approach for this type of requirement or should I do it in some other manner.
You could subclass an UITableViewCell, add UICollectionView as subview, conform UICollectionViewDataSource and UICollectionViewDelegate
here's sample code using xib
class CollectionViewTVC: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
}
extension CollectionViewTVC: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return items
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
// return size
}
}
extension CollectionViewTVC: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// select cell action
}
}
Why don’t you use two table view cells, one for every collectionViewu?

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