Multiple UICollectionViews in ViewController | Would not call one CollectionViews Delegate methods - ios

SO I have two UICollectionViews in my UIViewController in Storyboard and both are linked with delegate and datasource to my ViewController. All the associated UICollectionView delegate methods are implemented and checks for the UICollectionViews are implemented. But it's so frustrating that one UICollectionView is getting catered while the other one is getting completely ignored. I have scratched my head in all the available aspects but it is kind of putting me further towards the edge, please help.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.variantsCollectionView {
// let count = (item?.variant_groups?.count)!
return 1
} else {
return 2//(item?.extra_groups?.count)!
}
}
and
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
if collectionView == self.variantsCollectionView {
//IT DOESNT EVEN COME HERE AT ALL
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell_variant", for: indexPath)
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
//HERE IT COMES ALWAYS FOR THE NUMBER OF CELLS
return cell
}
}
Whereas the UICollectionViews are connected like this:
and:
Please please help. Thank you so much

Via comments the TS found solution by following these steps:
Ensure both collection views have non-nil data sources (and delegates).
Check that data source methods are executed for both collection view.
Check that both collection views' cells have valid size.
Finally the problem was found after checking the heights of each collection view inside stack view.
basically CollectionView has a specific height whereas
VariantCollectionView didnt, and both were in a stackView. When first
was created in view it took up the entire size where as the other one
kind of actually disappeared. Hence the issue.

Related

Unable to dequeue single cell in collection view

I have come across a very disturbing issue in which I am not able to dequeue only a single cell in collectionView. When there are more than one item in collectionView, it works fine but soon as I have an array with a single item, it doesn't show any cell. I am not able to figure it out what’s wrong with my code.
extension collectionview: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! UICollectionViewCell
return cell
}
}
The number returned for numberOfItemsInSection should be the total number of objects you want to show, so if you have an array of objects for example, you would want to return the count of that array. Since it's currently set to one, only one item will be shown. If you change that to match the number of items you're displaying, then the collectionView should cooperate.
First:
the returned of numberOfItemsInSectionwill be array.count for Example
var array = [model]()
Then in cellForItemAt you should configure your cell with data so
Add this block in your cell class
func configureCell(_ data: model) {
// code for cell view
}
Finally cellForItemAt will be
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! UICollectionViewCell
cell.configureCell(array[indexPath.item])
return cell
}
I hope this be helpful for you.
Needless to say, collection views work fine with only one cell. Your code is insufficient to manifest the problem you describe.
But here are a few observations/suggestions:
Add breakpoint or debugging log statement in numberOfItemsInSection and make sure it’s getting called. If not, then you likely have one of a variety of different possible problems:
Make sure you set the “data source” for the collection view (either in IB or programmatically). Merely conforming to UICollectionViewDataSource is not sufficient: You actually have to set the data source of the collection view.
Make sure you set the base class for your storyboard scene to be the view controller in question.
Make sure you put these UICollectionViewDataSource methods in the right object.
I notice that you've put these methods as an extension to collectionview.
Class names should, as a matter of convention, always start with upper case letter.
Usually the UICollectionViewDataSource methods are put in the view controller (or, less common, a dedicated object that you'll vend and to which keep your own strong reference). I'm not sure what to make of your class named collectionview.
But, in short, there is no problem having collection views with only one cell. Your problem rests elsewhere.

Nested collectionView in a TableView

I've been successfully nesting collections views into tableview for a while now.
What I still don't know how to do, is to do it while respecting the MVC pattern?
Right now I declare my tableview and its cells and in the cell (where the collectionView sits), I attach my collectionView (I got 1 per cell) and do the data mapping. It works, but it's spaghetti code where my View is acting like a controller.
I tried a few times to respect the MVC patterns. I can get my controller to control both my tableview and my collection. Where I struggle is to tell my collection View delegate which data it should pick as all I have as reference is the indexPath (of the collectionView) but not in which tableView that specific collectionView sits.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
return cell
}
The delegate only gives me the indexPath of the cell not which collectionView it is. To use a concrete example - let's assume that my tableview cells represents messages and that each message has a collectionView that controls reactions (like Discord). How do I tell my collectionView delegate which Message it is linked to?
Thanks a lot for the help!
This is very simple. Every view and its subclass has a property tag. You must have an IBOutlet or a simple reference to the CollectionView that sits in the TableViewCell. When you dequeue the TableViewCell just set the tag of your CollectionView equal to the indexPath.row of your tableViewCell like this:
tableViewCell.collectionView.tag = indexPath.row
Then in the UICollectionViewDataSource or UICollectionViewDelegate methods you may find which collectionView it is in other words which tableViewCell does this collectionView sits in. Here's an example:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
let dataObject = dataSourceArray[indexPath.row] as! YourDataModelObject
// Here you may set any property of the collectionView cell
return cell
}
P.S: It is not necessary to respect MVC to that extent. You may manage your CollectionView from the TableViewCell. Have a look at this example.

Call UICollectionView protocol methods to change the amount of cells

I am working on a Minesweeper game and I chose to use a UICollectionView for the grid of the game. I am trying to make it so the user can change the size of the board/grid. I have it all set up now all I need is to call the UICollectionView protocol methods like when it is set up initially. How can I do this?
These are the methods I implemented in ViewController.swift that I want to call again.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return board.squares.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// Get a SquareCollectionViewCell object
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SquareCell", for: indexPath) as! SquareCollectionViewCell
// Get square that the collection view is trying to display
let square = board.squares[indexPath.row]
// Set that square for the cell
cell.setSquare(square)
// Make a border for the cell
cell.layer.borderWidth = 2
cell.layer.borderColor = UIColor.black.cgColor
return cell
}
I tried collectionView.reloadData() but that did not work for me. I hope this isn't a duplicate question or super obvious. Maybe there is a better way to do what I am trying to do, but I am still new and this is what I came up with.
EDIT:
Ok I have edited my code and it works now! Yay! My new problem is I have a second ViewController to change the size of the board (like a settings view), and when I try to apply the changes to the board from the second ViewController I get this error next to the first change of collectionView:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
So I guess when I am viewing a different ViewController collectionView is nil? Is there any easy fix for this?

In UICollectionView, the order changes automatically

I have a big problem.
I scrolled in the UICollectionView.
When the drawing area is scrolled too much, the arrangement order has become disjointed.
I do not want to change the order even though scrolling.
What should I do?
help me.
let titles = ["1","2","3","4","5","6","7","8", "9", "10", "11", "12"] //titles
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = testCollectionView.dequeueReusableCell(withReuseIdentifier: "TestCell", for: indexPath) as! TestCell //customCell
let testTitle = cell.contentView.viewWithTag(2) as! UILabel
testTitle.text = titles[indexPath.row]
return cell // yeah, all done. is these code is normal. right? But when i scroll this UICollection, change the order in Outside drawing area.
}
You should definitely not be using viewWithTag. The reason for this is because cells are dequeued, which means that cells that have been taken off screen by scrolling are reused for the cells which are about to come on screen. This is done to save memory. Sometimes the problem with the order can be because previously used cells are not updated quick enough when they are presented to the user. The solution to this if you are using a network request can be to firstly to streamline your request and then cache any heavy data that is returned, such as images.
Your code should not be causing the cells to change order. I'm wondering if you have a problem with using viewWithTag. That's a fairly fragile way to find views in a collection view/table view cell.
You already have a custom cell type. I would suggest creating an outlet to your label in your custom cell type, and referencing the label that way:
class TestCell: UICollectionViewCell {
//(Connect this outlet in your cell prototype in your Storyboard
#IBOutlet titleLabel: UILabel!
}
//And in your view controller's cellForItemAt method...
cell.titleLabel.text = titles[indexPath.row]

UICollectionView with 2 separate design

I have UICollectionView and the selected cell should like the yellow one in pic. So how to have a separate design for the selected cell and how to draw that curve above it ?
Shall I use 2 separate UICollectionViewCell for this ? Or there is any alternate way to reuse the same cell on selection.
Shall I use 2 separate UICollectionViewCell for this ?
That's one way to go. Do this if there are more differences than just the one you described.
Or there is any alternate way to reuse the same cell on selection.
Sure, you can do that. Look at the two cells in your illustration, but consider that the grey part above each one as part of the cell. The black rectangle and yellow bulging rectangle are simply two different images that you draw in the background of the cell, and you can configure the same type of cell either way simply by changing that image. This is a good approach if other aspects of the cell, like positions of labels and such, are the same between both cells.
If this is the only different between that you want to make after selection, I think that there is no need to create two different UICollectionViewCells, instead, you need to keep a reference on indexpath.row(s) of selected cell(s) and check if this is the selected row, change/add a new background image.
For example:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
// here is the variable that should save the current selected row...
private var selectedRow: Int?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let's consider that you have a custom cell called "MyCustomCell"
// which has "backgroundImage" property...
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellID", for: indexPath) as! MyCustomCell
// checking based on the selected row
cell.backgroundImage = indexPath.row == selectedRow ? UIImage("yellow") : UIImage("default")
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedRow = indexPath.row
collectionView.reloadData()
}
}
Note that if you want to check on more than one row, you should declare a selectedRows as an array (or maybe as a set) of Ints.
Hope it helps.

Resources