Basically, in my project I have a collection view within a table view cell, it all works fine apart from trying to detect the number of items needed for the collection view which will vary based on what table view row they selected.
The collection view works fine if I set a fixed valued for the numberOfItemsInSection, but that value would only work for 1 table view cell. I've stored the string value of the selected table view row and try to pass it to my custom table view cell which contains the collection view so it can update but it always returns nil.
Any ideas on how to get it to automatically updated based on the selected table view row?
Below is my code:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categoryItems.count // THIS IS WHERE I GET AN ERROR
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CustomCollectionViewCell
//What is this CustomCollectionCell? Create a CustomCell with SubClass of UICollectionViewCell
//Load images w.r.t IndexPath
print(self.selectedCategory.description)
let newArray = starbucksMenu[selectedCategory]
//cell.image.image = UIImage(named: (allItems[selectedCategory]?[indexPath.row])!)
cell.label.text = categoryItems[indexPath.row]
//cell.layer.borderWidth = 0.1
return cell
That is my Collection View Code.
Below is the Table View Delegate function that sets the values:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
word = indexPath.row
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
switch cell.isExpanded
{
case true:
self.expandedRows.remove(indexPath.row)
case false:
self.expandedRows.insert(indexPath.row)
}
self.selectedCategory = categories[indexPath.row]
print(self.selectedCategory)
cell.selectedCategory = selectedCategory
//self.array = starbucksMenu[starbucksMenuCategories[indexPath.row]]!
//self.collectionView.reloadData()
cell.menuItems = allItems[selectedCategory]!
cell.categoryItems = allItems[selectedCategory]!
cell.isExpanded = !cell.isExpanded
self.itemsArray = allItems[selectedCategory]!
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
Related
I have a Horizontal UICollectionView like the horizontal Calender in iOS.
Paging is enabled but not allowsMultipleSelection.
self.allowsMultipleSelection = false
self.isPagingEnabled = true
There are only 5 cells per page.
let cellSize = CGSize(width: self.view.frame.width / 5 , height: 60)
CollectionView's height is also 60.
didSelectItemAt change background color to .red and didDeselectItem resets it to .white.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = .red
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = .white
}
}
The collection view has multiple sections and rows. If I select a cell in the first visible page and scroll, random cells are selected in the next visible pages. That is to say random cells are red in the next pages. I do not want this to be so. I want to select/change color of cells manually.
How can I fix this?
Don't forget that UICollectionView has embedded reusing mechanism, so you should deselect your cells in the method "prepareToReuse" directly inside the cell class.
Take a class-level variable, say index
var index = -1
As you have said that multiple selections are not allowed so the following will do the job for you
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
index = indexPath.item
collectionView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
if let cell = cell {
cell.backgroundColor = indexPath.item == index ? .red : .white
}
}
Whenever user tap on any cell we save the position in index variable and then call the reloadData() to notify collectionView about the change
In cellForRowAt we check if the current cell us selected we set the color to red otherwise white
First, if you want to preserve multiple selection, you have to remember your selected ones in an array since it would get lost if a cell gets recycled and reused. For that use something like a [IndexPath] type). If one selected cell is enough, you could use a non-array version of below code.
var selectedItems: [IndexPath] = []
Then, do your recoloring in your cell's cellForItemAt(:):
cell.backgroundColor = selectedItems.contains(indexPath) ? .red : .white
Your didSelectItemAt delegate function should look like:
if !selectedItems.contains(indexPath) { selectedItems.append(indexPath)}
collectionView.cellForItem(at: indexPath)?.backgroundColor = .red
and your didDeselectItemAt delegate function:
if let index = selectedItems.firstIndex(of: indexPath) { selectedItems.remove(at: index) }
collectionView.cellForItem(at: indexPath)?.backgroundColor = .white
This should actually work. Let me know if we have to do adjustments.
I have a collection view, and you can select the items in it and toggle them on and off by changing the background colour. The cells are toggled on/off thanks to a boolean I have in an arrow I made for all of the cells. I have saved the bool value but when I try to write them back into the array and use collectionView.reloadData()the app crashes. My collectionViewcode is:
extension OLLViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { //set the amount of items in the CollectionView to the amount of items in the OLLData dictionary
return OLLData.OLLCasesList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { //set each cell to a different mamber of the dict.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OLLCell", for: indexPath) as! OLLCell
cell.imageView.backgroundColor = OLLData.OLLCasesList[indexPath.item]._isSelected ? UIColor.orange : UIColor.clear //change colour if selected
let image = OLLData.OLLCasesList[indexPath.item]._imageName
cell.label.text = image
cell.imageView.image = UIImage(named: image)
let savedIsSelected = defaults.bool(forKey: Key.isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = savedIsSelected
//collectionView.reloadData() //when uncommented it crashes the app
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { //detect if case selected and reload CollectionView
let caseName = OLLData.OLLCasesList[indexPath.item]._imageName
print(caseName, OLLData.OLLCasesList[indexPath.item]._isSelected)
OLLData.OLLCasesList[indexPath.item]._isSelected = !OLLData.OLLCasesList[indexPath.item]._isSelected
defaults.set(OLLData.OLLCasesList[indexPath.item]._isSelected, forKey: Key.isSelected)
collectionView.reloadItems(at:[indexPath])
collectionView.reloadData()
if OLLData.OLLCasesList[indexPath.item]._isSelected == true { //if the item is selected, add to selectedCases array
selectedCases.append(OLLData.OLLCasesList[indexPath.item]._id)
selectedCaseNames.append(OLLData.OLLCasesList[indexPath.item]._imageName)
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
else if OLLData.OLLCasesList[indexPath.item]._isSelected == false { //remove from selectedCases array
selectedCases.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._id })
selectedCaseNames.removeAll(where: { $0 == OLLData.OLLCasesList[indexPath.item]._imageName })
print(selectedCases, selectedCaseNames) //debugging
numberOfSelectedCases.text = String(selectedCases.count)
}
}
._isSelectedis the boolean that says whether the cell is 'toggled'.
Any ideas would be greatly appreciated.
First of all, uncommenting that line will produce an infinite loop. cellForRowAt happens because the collection view is reloading, so calling a refresh while the collection view is refreshing is no good.
So your issue is that you don't know how to display selected cells in your collection view, right?
Here's a function that fires right before the collection view is about to display a cell:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
<#code#>
}
Inside this function, you should:
Cast cell into your OLLCell (safely if you want to be thorough)
Look at your data and see if the cell should be selected OLLData.OLLCasesList[indexPath.item]._isSelected
Ask your casted cell to change its colors/UI/appearance according to your ._isSelected boolean
Step 3 has a VERY important caveat. You should be changing the UI when ._isSelected is false AND when it's true. Because the collection view reuses cells, old UI state will randomly recur. So setting it every time is a good way to ensure the behavior you want.
Here's an example:
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
//Cast the vanilla cell into your custom cell so you have access
//to OLLCell's specific functions and properties.
//Also make sure the indexPath falls in the indices of your data
if let myCastedCell = cell as? OLLCell,
0 ..< OLLData.OLLCasesList.count ~= indexPath.item
{
myCastedCell.imageView.backgroundColor = OLLData
.OLLCasesList[indexPath.item]._isSelected
? UIColor.orange
: UIColor.clear
}
}
I'm new to swift and building iOS Application from the scratch (using swift 4) and want to do something like below.
1. Implement Multiple cell selections in UICollectionView,
2. Pass selected cells data to Server.
Please anyone can help me, how to do that? Tell me the process and supporting articles to do that.
Below is reference Image. Thanks in Advance.
Well, the best way to handle multiple selections in UICollectionView
Enable Multiple Selection
myCollectionView.allowsMultipleSelection = true
put this code in your cell awakeFromNib
override func awakeFromNib() {
super.awakeFromNib()
let view = UIView(frame: bounds)
self.backgroundView = view
let coloredView = UIView(frame: bounds)
coloredView.backgroundColor = UIColor.red
self.selectedBackgroundView = coloredView
}
you can get the selected indexPath items
let items = myCollectionView.indexPathsForSelectedItems
This basic example. You can change as per your data.
When you select any cell then you need to check that selected cell is already selected before or not.
If not then add selected cell indexPath in indexArray and selected cell value in valueArray.
If current selected cell is previously selected then remove indexPath from indexArray and also remove selected cell value from valueArray
on continue button press pass arrSelectedData to server or next screen.
Define below 3 array.
var arrData = [String]() // This is your data array
var arrSelectedIndex = [IndexPath]() // This is selected cell Index array
var arrSelectedData = [String]() // This is selected cell data array
//UICollectionView Delegate & DataSource
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.arrData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell : CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
if arrSelectedIndex.contains(indexPath) { // You need to check wether selected index array contain current index if yes then change the color
cell.vw.backgroundColor = UIColor.red
}
else {
cell.vw.backgroundColor = UIColor.lightGray
}
cell.layoutSubviews()
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
let strData = arrData[indexPath.item]
if arrSelectedIndex.contains(indexPath) {
arrSelectedIndex = arrSelectedIndex.filter { $0 != indexPath}
arrSelectedData = arrSelectedData.filter { $0 != strData}
}
else {
arrSelectedIndex.append(indexPath)
arrSelectedData.append(strData)
}
collectionView.reloadData()
}
}
You can write the code like this to Enable Multiple Selection :-
yourCollectionViewName.allowsMultipleSelection = true
then you can Do it like this to see the cell Selected -
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath)
if cell?.selected == true {
cell?.backgroundColor = UIColor.orangeColor()
}
}
To Deselect You can do something Like this -
func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath)
cell?.backgroundColor = UIColor.clearColor()
}
Enable Multiple Selection
collectionView.allowsMultipleSelection = true
Overrider isSelected property of collectionViewCell.
override var isSelected: Bool {
didSet {
if self.isSelected {
//You can change this method according to your need.
setSelected()
}
else {
//You can change this method according to your need.
setUnselected()
}
}
}
func setSelected(){
bgView.layer.borderWidth = 4
bgView.layer.borderColor = UIColor.Palette.darkBlue.cgColor
bgView.backgroundColor = .blue.withAlphaComponent(0.2)
}
func setUnselected(){
bgView.layer.borderWidth = 0
bgView.backgroundColor = .white
}
You can print selected cell's indexPath
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(collectionView.indexPathsForSelectedItems)
}
Well, to achieve a thing like that, you need to mainly perform the following tasks
Whenever user clicks on a particular cell, you need to change the background colour for that item in the didSelectItemAt delegate method of UICollectionView
Now to send that data to server, you need an array to store all the selected cells and then send that array to server . You can perform the same in didSelectItemAt method as well
I can show you a prototype of what the function will look like:
Let's assume you have an array named arrayForPopulating for populating data inside Collection View and we have array named finalSelections which consist of names of all the selections that user made
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
// Change the background colour of the cell here
cell.contentView.backgroundColor = UIColor.red
// Add the selected cell's data to the array
finalSelections.append(arrayForPopulating[indexPath.row])
}
Now you can send you finalSelections array to the server !
I have inserted a collectionView inside my tableViewCell. Tableview contains the list of categories and the collectionView contains all the product. How can I have a different number of items in the collectionView based off of which table view row was selected? I've tried storing the selected table view row and using that to define the number of items to be returned however it either crashes with no error code, tells me the value is nil or just does not display any clitems in the collectionView. Any help would be greatly appreciated. Thank you for your time.
Below is my code:
My Custom table view cell:
extension ExpandableCell: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let toReturn = categoryItems.count
return counter
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CustomCollectionViewCell
//What is this CustomCollectionCell? Create a CustomCell with SubClass of UICollectionViewCell
//Load images w.r.t IndexPath
print(self.selectedCategory.description)
let newArray = starbucksMenu[selectedCategory]
//cell.image.image = UIImage(named: (allItems[selectedCategory]?[indexPath.row])!)
cell.label.text = categoryItems[indexPath.row]
//cell.layer.borderWidth = 0.1
return cell
}
My table view delegate method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
word = indexPath.row
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
switch cell.isExpanded
{
case true:
self.expandedRows.remove(indexPath.row)
self.selectedCategory = ""
case false:
self.expandedRows.insert(indexPath.row)
}
self.selectedCategory = categories[indexPath.row]
print(self.selectedCategory)
//self.array = starbucksMenu[starbucksMenuCategories[indexPath.row]]!
//self.collectionView.reloadData()
cell.menuItems = allItems[selectedCategory]!
cell.categoryItems = allItems[selectedCategory]!
cell.isExpanded = !cell.isExpanded
self.itemsArray = allItems[selectedCategory]!
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
I've tried many things, I've tried adding the items in an array and returning the count (displays nothing). I have a dictionary with the necessary items so I've also tried returning allItems[selectedCategory]?.count and this always returns an error, I believe selectedCategory has no value once this is called.
Make a for loop of collection view item with appropriate operation Between beginUpdate() and endUpdate()
So the thing is I have 4 UICollectionViewCells inside CollectionView that is inside a TableView. (I set the TableViewController as the DataSource and Delegate of the CollectionView).
Now, I've stored a string array of 4 elements inside a CKRecord. How can I set the label inside the 4 cells, so that they display each string of the array?
Here's what it looks like:
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CvCell
let poll = polls[indexPath.row] // polls is a [CKRecord]()
let labelContent = poll["4strings"] as? [String]
cell.cellLabel.titleLabel?.text = labelContent
return cell
}
Now obviously, that's not going to work since I basically set the label of each cell to the array itself. How can I write a for loop that goes through each CvCell label, or rather how can I specify the label of e.g. the third cell to set it to the value of labelContent[2]?
UPDATE:
Totally forgot to mention, the data structure in the cloud basically looks like this:
array1 = [1a, 1b, 1c, 1d]
array2 = [2a, 3b, 2c, 2d]
array3 = [3a, 2b, 3c, 3d]
array4 = [4a, 4b, 4c, 4d]
And if I try to do what #user3353890 proposed, it's giving me the following results for my tableview:
tableview1cell - collectionview1 : [1a, 2b, 3c, 4d] -> these are the collectionviewcell labels
tableview2cell - collectionview2 : [1a, 2b, 3c, 4d]
tableview3cell - collectionview3 : [1a, 2b, 3c, 4d]
tableview4cell - collectionview4 : [1a, 2b, 3c, 4d]
However what I want is:
tableview1cell - collectionview1 : [1a, 1b, 1c, 1d]
tableview2cell - collectionview2 : [2a, 2b, 2c, 2d]
tableview3cell - collectionview3 : [3a, 3b, 3c, 3d]
tableview4cell - collectionview4 : [4a, 4b, 4c, 4d]
I'm sorry I have a really hard time explaining this, but I hope someone gets what I'm trying to do?
First, you're not returning one cell. You're going to ultimately return 4 cells because you stipulated that you want 4 items in each section in the numberOfItemsInSection method.
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 4
}
cellForItemAtIndexPath coordinates how you want to display each cell. Because you stated that there are 4 items in a section, this method will get called 4 times, returning 1 cell each time it is called to be an item in that section.
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CvCell
let poll = polls[collectionView.tag] // polls is a [CKRecord]()
let labelContent = poll["4strings"] as? [String]
// This should give you the string that you want.
let myString = labelContent[indexPath.row]
// Display the string in the label.
cell.cellLabel.titleLabel?.text = myString
return cell
}
After you set the labelContent array, get myString at each index by passing indexPath.row into the array. So for the 1st cell (0 index) it gives you the 1st string in your array (0 index).
Edit
when you create poll, use indexPath.section in order to keep the correct order of all your arrays while displaying data.
let poll = polls[collectionView.tag]
Edit 2
In your tableView cellForRowAtIndexPath Method, when you create a cell, set the cell's collectionView.tag to indexPath.section
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! CustomCell
cell.collectionView.delegate = self
cell.collectionView.dataSource = self
cell.collectionView.tag = indexPath.section
return cell
}
Then when you dequeue your collectionView, you can access the proper array by calling collectionView.tag -as seen above- in order to get the indexPath of the tableViewCell.
A better solution so you don't have to nest a collectionView within a tableView:
This gives you the number of sections you want. One section for each array in your "polls" array:
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return polls.count
}
This will take the sub-array for each corresponding section and allow the number of items in that collectionView section to correspond with the number of items in the array:
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
let poll = polls[section] as? [String]
return poll.count
}
Display your collectionView cells here:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CvCell
let poll = polls[indexPath.section] // polls is a [CKRecord]()
let labelContent = poll["4strings"] as? [String]
// This should give you the string that you want.
let myString = labelContent[indexPath.row]
cell.cellLabel.titleLabel?.text = myString
return cell
}