Display different tableview data depending on which collectionview cell is selected - swift

Here's a screenshot of the UI
I have a collectionview above my tableview. I'm trying to get the tableview's data to change based on which collectionview cell is selected.
This view handles menu item selection, and menu items are displayed in the tableview. The categories for these menu items are displayed in a horizontally-scrolling collectionview right above the tableview.
The restaurant class has an instance variable .categories which returns an array of strings. This is what populates the collectionview. The menu item class has an instance variable .category which returns a single string. I intend to match these two.
My intended result: For each selected cell in the collectionview, take its category name and communicate it to the tableview. The tableview should take this name and filter through its menu items to check for matching category names. Display those specific tableview cells.
For my collectionview:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = restaurant?.categories.count {
return count
} else {
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! CategoryCell
// Sets label text and images for each category
cell.categoryLabel.text = restaurant?.categories[indexPath.row]
if UIImage(named: (restaurant?.categories[indexPath.row])!) != nil {
cell.buttonView.image = UIImage(named: (restaurant?.categories[indexPath.row])!)
}
return cell
}
For my tableview:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = restaurant?.menuItems.count {
return count
} else {
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = masterTableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! MenuItemCell
cell.selectionStyle = UITableViewCell.SelectionStyle.none
let currentItem = restaurant?.menuItems[indexPath.row]
cell.item = currentItem
return cell
}
Right now, my result (obviously) is that simply all of my menu items are being displayed in the tableview, regardless of which collectionview cell is selected.

Two things needed in following format for your question.
1. restaurant?.categories values:
[popular, milk tea, snacks, tea, ...]
2. restaurant?.menuItems values:
{
popular : [
{
"name" : "thai-tea",
"price": "$6",
"thumbnailURL" : "https://.../thai.png"
},
{
"name" : "milk-tea",
"price": "$3",
"thumbnailURL" : "https://.../ml.png"
}
],
snacks : [
{
"name" : "brownie",
"price": "$7",
"thumbnailURL" : "https://.../brw.png"
},
{
"name" : "pasta",
"price": "$3",
"thumbnailURL" : "https://.../pas.png"
}
]
}
Global Variable
var whichCellSelect : Int = 0
CollectionView
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
whichCellSelect = indexPath.item
yourTableView.reloadData()
}
Tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = restaurant?.menuItems.count {
return count
} else {
return 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = masterTableView.dequeueReusableCell(withIdentifier: "menuCell", for: indexPath) as! MenuItemCell
cell.selectionStyle = UITableViewCell.SelectionStyle.none
let currentCategory = restaurant?.categories[whichCellSelect] // You will get String here
let currentMenuItem = restaurant?.menuItems[currentCategory] // You will get Array here
cell.item = currentItem
return cell
}
Notes
Make sure, restaurant?.categories data type as [String] and restaurant?.menuItems data type as [String : Any] . This is one of the simplest way to understand data transfer within UIViewController

How are the different categories stored? Are you using an array for each category and sending that specific array to the TableView after an option in the CollectionView is selected? Then you would need to refresh the TableView so the changes are reflected in the interface.
I alsso can't see where you're getting what the user pressed in the CollectionView. ... can you not use something like this ...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
}
to get what they've selected, use that choice to update the table by writing a different array or only adding certain array elements depending on the category then use reloadData() to refresh the table.

Related

Showing/Hiding images in CollectionView Cell

I'm trying to show some images and hide others using ".isHidden" in my CollectionView. But when I scroll down or reload the collectionView they either get reordered incorrectly or hidden entirely.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
for star in cell.starImgOutletCollection {
if star.tag <= item.starRating {
star.isHidden = false
} else {
star.isHidden = true
}
}
return cell
}
Edit: Here is my prepareForReuse
override func prepareForReuse() {
super.prepareForReuse()
for star in starImgOutletCollection {
star.isHidden = true
}
}
Assuming your "stars" are 5 image views in a stack view like this:
And, assuming your starRating will be between 0 and 5 (Zero being no rating yet)...
In your cell class, create a reference to the stack view - since your question mentions starImgOutletCollection I'm assuming you are using #IBOutlet (that is, not creating your views via code), so:
#IBOutlet var starsStackView: UIStackView!
Then, still in your cell class, add this func:
func updateStars(_ starRating: Int) {
for i in 0..<starsStackView.arrangedSubviews.count {
starsStackView.arrangedSubviews[i].isHidden = i >= starRating
}
}
Now, in cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ReadBookCell", for: indexPath) as! ReadBookCell
let item = readBookArray[indexPath.item]
cell.updateStars(item.starRating)
// do the other stuff to set labels, images, etc
// in the cell
return cell
}
You no longer need the outlet collection for the "star" image views, and you no longer need to implement prepareForReuse().

UICollectionView inside UITableViewCell how pass data to Uicollectionviewcell

I have a tableview controller with a collectionview inside it , in the tableviewcell i implemented a collection view with horizontal scrolling, the data appears not correct
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = friendsCollectionView.dequeueReusableCell(withReuseIdentifier: "friends", for: indexPath) as! friendCollectioncell
friendsCollectionView.delegate = self
friendsCollectionView.dataSource = self
let ava = Myfriends[indexPath.item]["ava"] as! String
let firstname = Myfriends[indexPath.row]["firstName"] as! String
let lastname = Myfriends[indexPath.row]["lastName"] as! String
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Myfriends.count
}
problem is in
return Myfriends.count
always empty so not thing appears but if a change to 1 or 2. it works ok
in uitableviewcontroller:
var myFriends: [Dictionary<String, Any>] = []
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// section 1 which includes 1 cell that shows my friends
if indexPath.section == 0 {
// accessing cell in main.storyboard that has id "friends". This cell stores my friends
let cell = tableView.dequeueReusableCell(withIdentifier: "friends", for: indexPath) as! friends
cell.Myfriends = myFriends
}
return cell
// section 2 (or any other sections) that shows all the posts of the user
} else {
return cell
}
Thank You in advance
Edit:
If I understand this right:
problem is in return Myfriends.count always empty so not thing appears but if a change to 1 or 2. it works ok
probably your collectionView(_:numberOfItemsInSection:) is called before you this cell.Myfriends = myFriends is set.
In your friends tableViewCell:
class friends: UITableViewCell .. {
...
var Myfriends: [Friend] {
didSet {
friendsCollectionView.reloadData()
}
}
}
Imho your collectionView(_:numberOfItemsInSection:) isn't called at all.
This code
friendsCollectionView.delegate = self
friendsCollectionView.dataSource = self
in your collectionView(_:cellForItemAt:) is useless, because if this method is called, dataSource (and probably delegate) are already set correctly.
Instead, change your tableView(_:cellForRowAt:) to
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView...
cell.friendsCollectionView.delegate = cell
cell.friendsCollectionView.dataSource = cell
return cell
} else {
...
}
}
or set friendsCollectionView dataSource and delegate in your friends cell, where you init your collectionView.
If that shouldn't help you, check where and how you populate your myFriends dictionary.
Besides that, I'd recommend that you create a struct Friend:
struct Friend {
let ava: String
let firstName: String
let lastName: String
}
and change your var myFriends to
var myFriends: [Friend] = []
That would make it easier and safer to access the values for your friendsCollectionCell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = friendsCollectionView...
let friend = MyFriends[indexPath.item]
let ava = friend.ava
...
return cell
}

How to programmatically set up a dynamic number of collection views in Swift

I have a list of many Decks, which is mainly has a name and a list of Cards. The below shows the class structure.
class Deck{
var id:String
var name:String
var cards:[Card]
init(id:String, name:String, cards:[Card]) {
self.id = id
self.name = name
self.cards = cards
}
}
class Card {
var id:String
var name:String
init(id:String, name:String){
self.id = id
self.name = name
}
}
I need to be able to create a single UICollectionView for each deck, where each cell should represent a button with the single card's name, and I need to be able to identify which cell's button is tapped. This means that I need to allow both dynamic number of UICollectionView(list of decks) and a dynamic number of UICollectionViewCell(list of cards). Please note that I already understand how to create the cells dynamically and identify which cell's buttons are tapped.
Overall, the main issue lies in the fact that I won't know how many decks and hence how many UICollectionViews I have to create. But, since the structure across all UICollectionViews are the same but populated with different values (i.e number of cells, cell's label title has different values), I am wondering what is the way to dynamically create UICollectionViews (NOT UICollectionViewCells) and populate them with decks from a list like decks:[Deck]
Thanks a bunch! The image at the bottom shows roughly what I would like to do.
It's still not clear if you really need a collection of UICollectionView objects or you could do with multiple sections in the same collection view. Anyhow, if you do like multiple collection views just make a collection or table view displaying decks list and put collection view for the deck in the cell.
class DeckCell: UICollectionViewCell { // or class DeckCell: UITableViewCell
var deck: Deck = .empty
#IBOutlet weak var collectionView: UICollectionView!
}
extension DeckCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.deck.cards.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath)
cell.card = self.deck.cards[indexPath.row]
return cell
}
}
Here is simple solution for you.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//number of items in Decks Array will be its count
if collectionView == dockCollectionView {
return Decks.count //It will tell the collection view how much cell will be there
}
if collectionView == cardsCollectionView {
return Cards.count
}
}
//Setup your resuable cell here
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//Setup for Dock CollectionView
if collectionView == dockCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Dock Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = Decks[indexPath.row].name
return cell
}
//Setup for Cards CollectionView
if collectionView == cardsCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cards Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = Cards[indexPath.row].name
return cell
}
}
//Now get to know which cell is tapped
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == dockCollectionView {
print(Decks[indexPath.row].name)
}
if collectionView == cardsCollectionView {
print(Cards[indexPath.row].name)
}
// Here you write a code when user select a particular cell
}
You already have an solution you have a list in which you have a items so you have to implement dynamically so you just need to count a number of item's in a list.
So for do that you use items.count in to func:-
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = item[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Here you write a code when user select a particular cell
}

Index out of range : UICollectionView inside a UITableViewCell

I'm trying to put a UICollectionView in a UITableViewCell, but I guess I'm doing wrong...
So here is my code :
PanierMenuCell is UITableViewCell
extension PanierMenuCell : UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return listProduit.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellIdentifier = "PanierCollectionCell"
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? PanierCollectionCell else {
fatalError("erreur de conversion !!")
}
cell.ONom.text = listProduit[indexPath.row].nomProduct
cell.OQtt.text = "x\(listProduit[indexPath.row].nbProduct)"
cell.OImage.image = UIImage(named: "test")
cell.backgroundColor = .gray
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.cornerRadius = 5
return cell
}
}
Here is my class calling the UITableViewCell :
extension PanierVC : UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listPanierProduct.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "PanierMenuCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PanierMenuCell else {
fatalError("erreur de conversion !!")
}
let produit = listPanierProduct[indexPath.row].product as! Menu
let listProduit = produit.getListProducts()
cell.listProduit = listProduit
cell.OTotal.text = "Total: \(produit.prix)0€"
cell.ONomMenu.text = "Menu : \(produit.nom)"
cell.backgroundColor = .gray
return cell
}
So here is the problem :
When I'm using several cell with collectionView inside without scrolling the tableview, that's working well, the problem is when I need to scroll down the tableView (the tableview is so reloading the cell), that's causing an error : "Thread 1: Fatal error: Index out of range" in
cell.ONom.text = listProduit[indexPath.row].nomProduct
The item insides the UICollectionView are king of mixed together, I mean that a tableviewcell will take some items which are suppose to be in other tableviewcell, like if the cell.listproduit was mixed... Don't know how clear I was, but I can explain more if needed.
Any ideas on how I should solve this ?
Thank's a lot
You need to reload your UICollectionView after changing the data source. This will update the number of items in the collection view.
So after this line:
cell.listProduit = listProduit
Add this line (Assuming you have a property referencing your UICollectionView):
cell.collectionView.reloadData()

Images In a Collection View

I am coding in Swift 3.2, and inside of a TableView Controller have a TableView (scrolling vertically), and each row of the table holds a CollectionView (scrolling horizontally).
I am not sure if it is acceptable coding practice, but I made my ViewController class inherit UITableViewController and UICollectionViewDelegate and UICollectionViewDataSource.
For now, I have a hard coded array of image names and would like three images to scroll horizontally in each collection view, with each row having distinct images. However, as of now each row shows the same three images ("11.png", "12.png", "13.png"), and I was wondering how to fix this.
Below is my ViewController class.
//for the tableView
let event = generateRandomData()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return event.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
return cell
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
}
//for the collectionView
var images: [String] = [
"11.png", "12.png", "13.png",
"21.png", "22.png", "23.png",
"31.png", "32.png", "33.png",
"41.png", "42.png", "43.png",
"51.png", "52.png", "53.png",
"61.png", "62.png", "63.png",
"71.png", "72.png", "73.png",
"81.png", "82.png", "83.png",
"91.png", "92.png", "93.png",
"101.png", "102.png", "103.png",
]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
collectionView.isPagingEnabled = true; //makes the horizontal scrolling have no bounce and relatively have a better snap
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath as IndexPath) as! CollectionViewCell
let myCellImage = UIImage(named: images[indexPath.row])
myCell.eventImage.image = myCellImage
return myCell
}
I am an amateur coder, so any help is greatly appreciated!
I am sure this problem already have an answer somewhere, but since I can't find that I'll just give one here.
Your mistake is here:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
/...
let myCellImage = UIImage(named: images[indexPath.row]) //<< indexPath.row will return 0,1 and 2 for each of your collection view
/...
}
Each collection view in your table view does not communicate amongst themselves to form a larger collective view. Since all of the are provided the int of 3 for numberOfItemsInSection and their numberOfSections defaulted to 1(you did not set any), each of them will request for item at indexpath (0,0), (0,1) and (0,2) when trying to populate their data.
Your hardcoded array however, is an array with 30 items. So each collection view when requesting for cellForItemAt will only get 0,1 and 2 for images[indexPath.row]. Also you should use indexPath.item for collection view, indexPath.row is for table views, it may or may not work if used wrongly.
Solution
You can just set the tag property of the collection view to mark it's row:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
/...
//Make sure you have a subclass for UITableViewCell that have an IBOutlet that connects to the collection view
cell.collectionView.tag = indexPath.row //Note that this only works in your situation where the table view have only 1 section.
/...
}
Then split your array into a 2D array:
var images = [
["11.png", "12.png", "13.png"],
["21.png", "22.png", "23.png"],
["31.png", "32.png", "33.png"],
["41.png", "42.png", "43.png"],
["51.png", "52.png", "53.png"],
["61.png", "62.png", "63.png"],
["71.png", "72.png", "73.png"],
["81.png", "82.png", "83.png"],
["91.png", "92.png", "93.png"],
["101.png", "102.png", "103.png"]
]
Finally read the array in the collection view like:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
/...
let myCellImage = UIImage(named: images[myCell.tag][indexPath.item])
/...
}