Collection view inside table view - swift

I have the following problem.
In my project I have a table view with multiple sections and one row in every section. Inside row I have a collection view.
Collection view items count depending on section in which the collection is located.But I do not understand how to access section number when I call func numberOfItemsInSection for collection view.
Here is my code:
ViewController.swift
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
TableCell.swift
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//TODO ?????? How can i get here section of table view
}
Thanks!

Create an array in UITableViewCell subclass and use that array in collectionview datasource methods
class CustomCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
var collectionData = [String]()
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return collectionData.count
}
}
In tableView cellForRowAt method assign value to the array and reload colletion view
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as? CustomCell
cell?.collectionData = []//assign data here based on indexPath.section
cell?.collectionView.reloadData()
return cell!
}

Here you can use object oriented approach, in Your tableview cell create a variable tableSectionNumber. Like
class YourCell: UITableViewCell {
public var tableViewSectionNumber: Int = 0;
....
Now here in collectionView numberOfItemsInSection method, you can access it
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//TODO ?????? How can i get here section of table view
self.tableViewSectionNumber // its here
}
Now you can set this value in the cell in your ViewController, when you are populating the cell in TableView cellForRowItem method, Like
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! YourCell
cell.tableViewSectionNumber = indexPath.section // here you set the section number in cell
}

Related

How to Navigate to new View Controller from collection view cell? In Swift

I have two View controllers(VC-1, VC-2).
View controller 1 is having a table view and further that table view is consists of 4 collection views. I want to navigate to new VC(VC-2). But unable to get "self.storyboard.instantiateViewController" in didSelect method of collection View.
So now how I can navigate to VC-2
You should use delegate. you can create a protocol and define a method for routing and implement it into VC1
You can use the below sample code.
This code is using closure syntax
class FirstVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell_Identifier") as! TableCell
cell.configureCell(data: ["Your", "Data"])
cell.didSelectRow = { data in
// Goto second view controller
let vc = self.storyboard?.instantiateViewController(withIdentifier: "SecondVC")
self.navigationController?.pushViewController(vc!, animated: true)
}
return cell
}
}
class TableCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var colView: UICollectionView!
var didSelectRow: ((_ data: String) -> Void)? = nil // Closure
var arrData = [String]()
func configureCell(data: [String]) {
// Setup CollectionView data
self.arrData = data
self.colView.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.arrData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CELL_IDENTIFIER", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let data = self.arrData[indexPath.row]
didSelectRow?(data)
}
}

1 section and 2 cells but 4 indexPath.row

Swift 4.
I want to return two cells in one section, so in my class CollectionViewController I do :
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1 }
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2 }
I see two cells but if I print indexPath.row in the code below (still in the same class), I see 0 1 0 1. Why is it not only one 0 1as I have only two cells in one section ?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CollectionViewCell
print(indexPath.row)
return cell
}
cellForItemAt is getting called four times. Your numberOfItemsInSection is hard-coded for 2. When the view loads cellForItemAt is called twice (once for each cell). It is then getting called twice again when you call reloadData() from your DispatchQueue.main.async closure.
Updated - How to avoid the first call:
You need to store your cell data in an array, and only populate the array just before you call reloadData(). Thus, the array will be empty when the view is first loaded.
var yourArray = [YourObject]() //Empty Array
//..
DispatchQueue.main.async {
//append your two items to the yourArray
yourArray.append(/*cell1data*/)
yourArray.append(/*cell2data*/)
self.collectionView?.reloadData()
}
//..
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return yourArray.count
}

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])
/...
}

Index path of a collection view cell that is inside of a table view cell

I have a UICollectionView nested inside of a table view cell. How can I get the index path of the horizontal row of the collectionview cell? I tried to use
let index = cell.tag
print(index as Any)
to print which index was being selected not knowing that it will print a value of zero no matter what cell I select. I apologize that I am not experienced with collection views. Any help is much appreciated. Thanks.
You should design a protocol handling nested UIElement interactions. It makes your mind clearer and allocate better addresses
class ViewwithTable:UIViewController,UITableViewDataSource,collectionCell_delegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "temp", for: indexPath) as! NestedViewwithCollection
cell.delegate = self
return cell
}
func didPressed(indexPath: IndexPath) {
//the pressed index!
}
}
class NestedViewwithCollection:UITableViewCell,UICollectionViewDataSource,UICollectionViewDelegate{
var delegate:collectionCell_delegate?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let del = delegate{
del.didPressed(indexPath: indexPath)
}
}
}
protocol collectionCell_delegate {
func didPressed(indexPath:IndexPath)
}
Use this code for tableView
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return model.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
return cell
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
guard let tableViewCell = cell as? TableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
}
Collection View
extension ViewController: UICollectionViewDelegate,UICollectionViewDataSource
{
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return model[collectionView.tag].count
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",
forIndexPath: indexPath)
cell.backgroundColor = model[collectionView.tag][indexPath.item]
return cell
}

Why does my UICollectionViewController not show anything?

I'm trying to make a UICollectionViewController show a grid of cells (in which I'll later add text, likely as textViews if possible). I have:
class GridViewController : UICollectionViewController{
//(NOW GONE) #IBOutlet weak var gridViewTable = UICollectionViewController()
let arr1 : [String] = []
let arr2 : [String] = []
override func viewDidLoad(){
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "gridCell")
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 4
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.red
return cell
}
}
I used the interface builder to connect white where the cells will go to the gridViewTable. Nothing shows up at all, even tho it runs and compiles. How can I get the cells to show?
please use these code:
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 4
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.red
return cell
}
You don't need the outlet for a UICollectionViewController, the class itself takes care of that.
Also, make sure that the view controller's class in Interface Builder is set to GridViewController in the Identity Inspector (3rd tab from the left in the right-most pane).