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?
Related
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.
I'm trying to make a store for a game where you can buy different colors of balls. I'm using a UICollectionView with all white balls to begin with, when I click a cell, it changes the white ball image to a colored ball image (EDIT: an image from a pre made array of colored images). when I scroll down and scroll back up, the cells I selected are reset to the white ball image. I don't want this obviously.
I've tried using the method already built into the UICollectionView class with didSelectItemAt but when I scroll down and back up it gets all messed up (When i select a cell a different one's image is changed not the correct one). I've tried using isSelected in the collectionViewCell class but I can't get the indexpath in here so I can't save which cells are selected.
override var isSelected: Bool{
didSet{
if self.isSelected
{
textImage.image = images[indexPath.item] // I don't know what to put here I don't have the indexPath
}
else
{
textImage.image = #imageLiteral(resourceName: "circleWhite")
}
}
}
Any help is great, I am fairly new to coding in Xcode so some explanation of what to do here is very much appreciated.
EDIT: I have an array of images that should be the store, not just one different color, multiple colors. When I click on a cell, it should access the image in the corresponding index in the array and use that image to replace the white circle.
Did the same thing in our code.Below is the solution for this.
1.Take an array of tuple to maintain selected status and specific colors or what ever you want.
var arrColor = [(isSelected:Bool,color:UIColor)]()
2. Now do the below code on cellForItemAt.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectionViewCell = self.iconCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? EditDeviceCollectionViewCell else { return UICollectionViewCell() }
if arrColor[indexPath.item].isSelected{
arrColor[indexPath.item].color = .white
}else {
arrColor[indexPath.item].color = .black
}
return collectionViewCell
}
3.Now write the data source method and use below for color
//MARK:- UICollectionViewDelegate
extension yourViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
arrColor[indexPath.item].isSelected = true
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
arrColor[indexPath.item].isSelected = false.
}
}
Happy Coding 😊
When you scroll off the screen the cells get prepareForReuse called on them. What you need to do is store the state of the color somewhere else - like on the collectionView or a viewModel. And when cellForRow is called you pull the color to show for that row from the saved state variable.
Essentially what’s happening is the cells are being reused when they go off screen to save memory. So when you scroll back to them they are re-created often with the state of another cell since cells are reused.
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.
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]
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.