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()
Related
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'm very new to iOS development and Swift.
I have a UICollectionView that populates images from the array. My idea is to load initially 10 images and let user scroll to the bottom of the collectionView and then load next 10 images from the array by pressing button 'more' or just automatically.
Can you suggest the best way to do it?
My code is:
self.searches.insert(results, at: 0)
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//1
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! CatPhotoCell
// this methods takes one photo from searches array
let flickrPhoto = photoForIndexPath(indexPath: indexPath)
cell.imageView.image = flickrPhoto.thumbnail
cell.cat_name.text = flickrPhoto.title
return cell
}
private extension CatsPhotosCollectionViewController {
func photoForIndexPath(indexPath: IndexPath) -> FlickrPhoto {
return searches[(indexPath as NSIndexPath).section].searchResults[(indexPath as IndexPath).row]
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){
//step 2 here
}
1.Intially set array count as 10.
2.Increase array count whatever you want inside the above function and reload collectionView.
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()
}
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
}