i have a uiviewcontroller, where i put a uitableview, with custom cells. Each of these cells, have inside a uicollectionview, with a horizontal scrolling.
I am working programmatically, so the tableviewcell is the delegate/datasource for collectionview.
My datasource structure, is an array of arrays.
My tableview, has, for every tableviewcell, a tableview section.Tableview datasource methods work without problem using the array. I am able to set up the tableview correctly.
Inside this cell, i want to display a collectionview, with horizontal scrolling.
The problem which i am encountering is i cannot assign datasource correctly using the arrays to collectionview. What happens is when i load the tableview, and scrolling down, i see duplicate records, so for example the first 3 rows are displayed correctly(in terms of data and number of items),but from the 4th row, for example, data is duplicated, so i see again the records for the first 3 rows, and of course this does not have to happen, because if i scroll on the 4th line(the collectionview on the 4th row) the app crashes due to an index problem out of range.
This is simple to understand: lets say i have 10 cells on the first row, and 5 on the 4th row/or section, as you prefer... Scrolling the 4th row'scollection view will cause the crash. This is because of the wrong data which is actually the same from the first row: instead, i have only 5 cells to render...
Tecnique i am using is pretty simple: give each tableviewcell's tag the actual indexpath.section. Then in the collectionview, use this tag to loop through array and then the indexpath.item to get the correct array.
Lets get a look into the code now:
Tableview
func numberOfSections(in tableView: UITableView) -> Int {
return DataManager.shared.datasource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! DiarioTableViewCell
cell.tag = indexPath.section
return cell
}
TableviewCell
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: "cell")
collectionView.register(DiarioCVCell.self, forCellWithReuseIdentifier: "cellCV")
collectionView.delegate = self
collectionView.dataSource = self
DataManager.shared.istanzaCV = self.collectionView
addSubview(tableCellBG)
addSubview(collectionView)
setConstraints()
}
CollectionView
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
let tvCell = collectionView.superview as! DiarioTableViewCell
return DataManager.shared.datasource[tvCell.tag].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellCV", for: indexPath) as! DiarioCVCell
let celltag = (collectionView.superview as! DiarioTableViewCell).tag
cell.datasource = DataManager.shared.datasource[celltag][indexPath.item]
return cell
}
Notice i have read all the possibly related threads, even ashfurrow's article, and here on stack, but i was not able to find a solution.
Thanks for any help!
Try to reload your collection view when cell is showing. Cells are being reused to save memory so they are created only once - you are setting all data at init and it's staying there forever.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! DiarioTableViewCell
cell.tag = indexPath.section
cell.collectionView.reloadData()
cell.collectionView.collectionViewLayout.invalidateLayout() //just to be sure, maybe it's not necessary
return cell
}
Related
I am creating a scrollable view filled with monthly calendars. I'm using a collection view to display a calendar inside a table view full of calendars. So each table view cell is a calendar for a specific month, and each collection view cell is a day. I have a separate swift file for the tableview cell from the view controller. Since each tableview cell is going to look different (because different months), the tableview cell needs to know which row it is placed in inside the tableview during its creation in dequeque cell function.
tableView.dequeueReusableCell(withIdentifier: "CalendarTableViewCell", for: indexPath)
I need to get the indexPath in the "for: indexPath" parameter inside the tableview cell file because the collectionview inside the tableview cell gets created when the tableview cell is dequeued. The contents of the collection view depends on which tableview row it's in. So how do I get that parameter?
Sorry for the long explanation, please help if possible. Thank you!
Create an array in UITableViewCell subclass and use that array in collection view data source methods.
class MonthCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
let datesArray = [String]()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return datesArray.count
}
}
In tableView cellForRowAt method assign the date values and reload the collectionView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! MonthCell
cell.datesArray = []//dates based on indexPath
cell.collectionView.reloadData()
return cell
}
Or
Assign a reference to the table view cell index path in tableView cellForRowAt method
class MonthCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
var tableIndexPath:IndexPath?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let tableIndexPath {
// return value based on tableIndexPath
} else {
return 0
}
}
}
//cellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MonthCell") as! MonthCell
cell.tableIndexPath = indexPath
cell.collectionView.reloadData()
return cell
}
Loading all the date from the firebase to the tableview at a time but i wanted to load 10 items at a time when i scroll to bottom again 10 items should load to the tableview, below is my code
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = items[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "inciCell") as!
IncidentCell
cell.delegate = self
cell.setupCell(with: item, indexPath)
return cell
}
}
In the delegate method cellForRowAtIndexPath you may check something like indexPath.row == items.count - 1.
if it's true, then you get 10 items from your DB and append it to your `items.
then do tableView.reloadData() to update the table view with 10 new items.
You may also make some nice spinner in the bottom (tableFooterView) of the table view. But it could be the next step.
Hope it helps.
So I have collection view with 3 cells, each of which has a tableview in it. Obviously, each collection view cell has to load it's table view with different data for the table view cells.
My first thought was to make the collection view the delegate and datasrouce of the table view, but even so, the datasource will need to know which collection view cell is being loaded.
Also thought of making a separate class for the DS and delegate for both the collection view and the table view, but then again, I'm stuck on how the tableview DS will know which collection view cell it's being loaded from.
Any thoughts?
EDIT:
After I make the assignment, the datasource field is nil. So what am I missing here?
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) else {
return UICollectionViewCell.init()
}
cell.backgroundColor = UIColor.clear
cell.theTableView.dataSource = RequestedTableDataSource.init()
cell.theTableView.delegate = self
cell.theTableView.reloadData()
return cell
}
class RequestedTableDataSource: NSObject, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 16
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "RequestedCell") as? IntervieologistRequestedCell else {
return UITableViewCell.init()
}
cell.setupCellFor(name: "Dan", image: UIImage.init(named: "dan")!)
return cell
}
}
If I do this:
let foo = RequestedTableDataSource.init()
cell.myTableView.dataSource = foo
then the dataSource field is set, numberOfRows gets called, but cellForRowAtIndexPath doesn't.
You can set the tableView.tag to an unique integer and identify it later in the delegate methods using that, but I would recommend keeping a seperate datasource/delegate for each tableview, much cleaner.
Don't make the CollectionView or any other View object the datasource/delegate.
eg set
collectionViewCell.tableView.tag = index;
collectionViewCell.tableView.delegate = commonDelegate;
or
collectionViewCell.tableView.delegate = uniqueDelegate; // Better choice
Requirement :
I have a list of UITableviewCell loaded from a nib that I'm presenting on UITableview. The first time I open the UIViewController all cells are shown correctly and work as expected.
Issue :
If I navigate back to the parent and then open the UIViewController again the UITableviewCell are 'invisible'. I say invisible because with a breakpoint in cellForRowAt I can see that the table view does load all cells and the cells are valid.
Code :
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = (project?.sliderData.sliders[indexPath.row].view)! as UITableViewCell
print(cell.contentView.subviews.count)
if let left = cell.viewWithTag(2) as? UILabel {
left.text = "left"
}
if let middle = cell.viewWithTag(3) as? UILabel {
middle.text = "middle"
}
if let right = cell.viewWithTag(4) as? UILabel {
right.text = "right"
}
return cell
}
Screen Shot Image
Expected observation :
I was thinking that maybe the subviews of the cells get released because I don't have any bindings to them in IB. To test this I'm printing the count of subviews and writing some text to the subview labels. And everything seems to go fine, the cells are loaded and the labels are there but the cells just don't show up.
But then, if I scroll the TableView up and down a little to get some cells updated those cells do appear at the top and bottom of the view as shown in the pic.
You need to call dequeueReusableCell(withIdentifier: "cell") inside your code then will show your table cell. It will reuse cell for your all numbers of row data content.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
return cell
}
More Details : How to create uitableview with multiple sections in iOS Swift.
Did not find reason why the tableView behaves the way it does so I solved the issue by dequeueing default cells. The views provided by the slider objects are added as subviews to the dequeued cells. Now the subviews can of course be any UIViews.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "sliderCell")
if cell == nil {
cell = UITableViewCell.init(style: .default, reuseIdentifier: "sliderCell")
}
cell?.addSubview((project?.sliderData.sliders[indexPath.row].view)!)
return cell!
}
I try to display a simple cell with one label and an add item button. I can't get it to display correctly. I spent a lot of time but I can't find the solution.
This is the result:
There is no add item button and no correct row. I have just two items in coredata.
This is my code:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let data = items[indexPath.row] as Item
cell.textLabel?.text = data.body
return cell
}
What are the problems? Can anyone help me to display add item button, correct row count, and customize height of the cell correctly? Thanks in advanced.
First lets return the correct number of rows unto the table view.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellContent.count
}
Next lets get it to output unto the prototype cells.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
cell.textLabel?.text = cellContent[indexPath.row]
return cell
}
Also lets call the unto the view controller the following so that it can run properly.
ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
One last thing, your prototype's cell has the same identifier as the one in your code. In my code I would make sure that the identifier in the prototype cell is "Cell" since I called it in UITableViewCell(...reuseIdentifier: "Cell").
View picture to see where to add the identifier in your storyboard