Multiple collection view in table view scrolling at the same time - ios

I have collection view as rows of a tableview. The collection view only supports horizontal scrolling. When I scroll to the end of the collection view in the first row, and then scroll to the bottom of the tableview. The last collection view is also at the end position when I haven't even touched that yet.
I know that the issue is because of dequeueReusableCell of the tableview. I have tried using the prepareForReuse() in UITableViewCell.
I expect that whenever any other row of the table view should remain unaffected by the interaction done on rest of the table view rows.

You could store (and restore) the collection views' content offsets by using the table view's delegate methods:
var xOffsets: [IndexPath: CGFloat] = [:]
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
xOffsets[indexPath] = (cell as? TableViewCell)?.collectionView.contentOffset.x
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
(cell as? TableViewCell)?.collectionView.contentOffset.x = xOffsets[indexPath] ?? 0
}

Related

Change Parent Collection View cell background color when select child tableview?

I have the parent custom Collectionviewcell and inside I have the child custom Tableview. When I click table view I need to change the background color of collectionviewcell and tableview row background. I am trying achieve using gesture for tableview. but its not working.
try with protocols
first your tableview inform the collection cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
backgroundColor = .red
delegate?.didSelectItem(indexPath: indexPath)
}
then on your collectionView Cell
func didSelectItem(indexPath: IndexPath) {
self.backgroundColor = .blue
}
Why you don't use didSelect of tableView?
Some code should be easy to see how you use gestures.
I have created one main array, inside created another array for table content, and manage selection on tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath).
Please check demo : https://github.com/karan7698007/CollectionViewDemo.git

Use tableView willDisplay cell forRowAt indexPath for custom Cells (with custom height)

In my project I have a UITableView displaying custom cells of a fixed height.
When I segue to the ViewController, I want to run an animation in the tableViewCells. when i scroll down, and then back to the first cell I do not want to run the animation again.
To achieve this I am using tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath). I then use an array to remember which cells have been on the screen already.
My function looks like this:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let myCell = cell as! myTableViewCell
if !alreadyShown.contains(indexPath) {
alreadyShown.append(indexPath)
myCell.animate()
} else {
myCell.dontAnimate()
}
}
However, this seems to ignore the height of my cell. Therefore it runs for every indexPath that would have been displayed for standard cells (e.g. 17 times on an iPhone X), even though 14 of those 17 cells are not visible.
When i scroll down later, the animation is already finished for those 14 next cells.
Is there any way of telling the willDisplay cell function the cell height so that it knows which cells are actually going to be displayed? Or is there any other way of keeping score of which cells have actually been displayed already?
The solution was setting the estimatedRowHeight for the tableView to a number equal to or greater than the actual hight of the cells in viewDidLoad. This made sure that tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) is only called on the actually visible cells.

Reusing dynamic TableViewCells without having to maintain of each cell's state in Controller

I have a UITableView that uses a cell that has 3 expandable and collapsable subviews in them. I would prefer to maintain the state of these views in my UITableViewCell class itself (states as in collapsed or expanded)
Since they are reusable cells, currently, if I expand view 1 in cell A, and then scroll down to cell B, it's view 1 will be expanded. I don't want this. I want it collapsed. But, if I scroll back up to cell A, I want it to still be expanded.
Other than storing all of these states in an array or dictionary
var expandedViewOneCells: [Int] = []
var expandedViewTwoCells: [Int] = []
etc.
I would prefer to have the cells essentially of act individually and maintain their own state... But how would I do this when cells are reused? Keep in mind, I will always only have at most 3 of these kinds of cells, so can I set something like only reuse after 3 cells.
Would it be wise to keep an array of the cells I load, and then on cellForRowAt load the cell from that array based on the index and return it?
In your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell function try not to deque a cell but create a new instance of your cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCustomCell()
return cell
}
If you are loading your cell from a xib file you need a way to create your custom cell from that nib. Add the following method to your CustomCell class
static func loadFromNib() -> RequestTableViewCell {
let nib = UINib(nibName: "\(MyCustomCell.self)", bundle: Bundle.main)
let cell = nib.instantiate(withOwner: self, options: nil)[0] as! MyCustomCell
return cell
}
Then in your func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell this will create a new cell for every row and not reuse a cell when scrolling
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCustomCell.loadFromNib()
return cell
}
A solution like this may not be optimal if your table view has a lot of rows but for a SMALL amount of rows this should be okay
I see 2 solutions to your problem:
Use 3 View Controllers. They should never get destroyed, and add / remove the corresponding VC's view on top of the .contentView of the cell as it appears or goes off-screen. See the solution here http://khanlou.com/2015/04/view-controllers-in-cells/ The Custom Cell itself is just a view, shouldn't really be concerned with the state, but if we move that logic to a View Controller - we should be fine, an we are not violating MVC. Plus, the View Controller can keep track of the height of the view, based on the state, and heightForRow(at:) can ask it for that
I'd use a Stack View as this is a perfect scenario for it. I'd probably represent the Cell itself as another stack view. Not sure exactly what the views look like and how they change, but it may end up as simple as hiding / unhiding the second view from the Stack View that represent a "cell".

Collectionview in tableivew cell. this is bug of reuse

collectionview in tableviewcell. when tableview reuse cell. collectionview in tableivewcell keep position. this is bug? and how to separate for it. example : cell 1 and cell 10 is collectionview(horizontal). when i scroll cell 1, cell 10 also scroll same postion. please help me.
Create a Dictionary to remember the offset for each cell like:
var contentOffset: [Int: CGFloat] = [:]
When table view cell is being removed from screen store the offset of your collection view like
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YouTableViewCell else {
return
}
contentOffset[indexPath.row] = cell.collectionView.contentOffset.x
}
Now when cell is about to display restore the content offset like:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? YouTableViewCell else {
return
}
cell.collectionView.contentOffset.x = contentOffset[indexPath.row] ?? 0
}
It is not a bug, when table cell being reused, it also reuse the content which is the collection view which is scrolled. Keep Googling and check out the links:
ios 8 Swift - TableView with embedded CollectionView
https://medium.com/#gargankit476/multiple-collection-view-in-uitableview-ced7909a5af3

is there a callback method that tells when a UITableViewController has finished creating the cells of a UITableView?

Is there an iOS / Swift 3.0 callback method that tells when a UITableViewController has finished loading (creating the cells of) a UITableView (after calling the .reloadData on the table view (in Swift)?
(or alternative method)
The func you're looking for doesn't exist, but per your comment above, would it be possible to get each cell (or some model object it takes as a property) to make the API pre-load call? In this way, data will only be pre-loaded for visible cells. When new cells are scrolled into view, their data will be loaded in turn.
For example, in your cell:
class MyCell: UITableViewCell {
var someObject: MyObject {
didSet {
someObject?.loadData()
}
}
}
and in your view controller:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! "MyCell"
cell.someObject = myObjects[indexPath.row]
return cell
}
This way you're only loading data for cells in view. If you were to add:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.someObject.cancelLoading()
}
You can ensure that objects in cells no longer view aren't loaded if not required.

Resources