I have a UICollectionView that appropriately recognizes taps on its cells in its collectionView(didSelectItemAt: ) delegate method.
However, I then embedded a collection view within the cell itself (so that each cell has its own collection view inside of it), but now the parent cell is not recognizing any taps anymore, I'm assuming because the embedded collection view is eating them up.
Is there some property that needs to be set so that the original (parent) cells register taps again even with their embedded collection views?
This functionality can be confusing, as users are accustomed to "something else" happening when interacting with a control in a table view cell, rather than it selecting (or also selecting) the row itself.
However, if you really want to do that...
One approach is to use a closure in your cell. When you handle didSelectItemAt use the closure to tell the table view controller to select the row.
Note that Apple's docs point out:
Note
Selecting a row programmatically doesn't call the delegate methods tableView(_:willSelectRowAt:) or tableView(_:didSelectRowAt:), nor does it send selectionDidChangeNotification notifications to observers.
So, if you need to execute code when a table view row is selected, you'll need to call that yourself - something like this:
func myTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("My didSelectRowAt:", indexPath)
}
Using the code in my answer to your previous question...
In SomeTableCell add this closure setup:
public var didSelectClosure: ((UITableViewCell) ->())?
and, still in SomeTableCell:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("collectionView didSelecteItemAt:", indexPath)
print("Calling closure...")
didSelectClosure?(self)
}
Next, in cellForRowAt in the table view controller, set the closure:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "someTableCell", for: indexPath) as! SomeTableCell
cell.rowTitleLabel.text = "Row \(indexPath.row)"
cell.thisData = myData[indexPath.row]
cell.didSelectClosure = { [weak self] c in
guard let self = self,
let idx = tableView.indexPath(for: c)
else { return }
// select the row
tableView.selectRow(at: idx, animated: true, scrollPosition: .none)
// that does not call didSelectRowAt, so call our own func
// if we want something to happen on row selection
self.myTableView(tableView, didSelectRowAt: idx)
}
return cell
}
you can implement UICollectionViewDataSource & UICollectionViewDelegate methods of inner collectionViews inside the cells itself & pass the events with closure to main class with main UICollectionView.
Related
I have a collectionView within a tableview and I am trying to access the collection view's indexPath in order to get its contents upon selecting it.
Here is my code:
TableView
//This grabs the indexPath of the collectionView Selected with its contents
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedDeal = dealArray[indexPath.row]
}
ViewController
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let cell = tableView.dequeueReusableCell(withIdentifier: "SpecialPicTableViewCell", for: indexPath) as! SpecialPicTableViewCell
//Defines the selected
let deal = cell.selectedDeal
//Grabs selected information here
dealSelected = deal.title ?? ""
//Then segues
performSegue(withIdentifier: "goToDeal", sender: nil)
tableView.deselectRow(at: indexPath, animated: true)
}
However, the selection does not register with the TableView. Nothing happens as a result.
The only information in the collection view is information you put there. You shouldn't need to get any information out of the collection view cell because you already have it.
The code you posted dequeues (possibly creates) a cell, cleans it up and gets it ready for use (possibly reuse.) That cell that you essentially created will know nothing about the data that is in the cell that was selected.
What you should be doing instead, is referring to the array where you store the deals to display and figuring out what deal is at the selected index path.
In my UITableViewCell have button AddToCart buttons. As if my UITableView data is more than 10 means I have to scroll to see all data. So now if I will on first button of first UITableViewCell as I scroll down to see all records of tableview than automatically last or second last button will also click I am unable to find the problem why this is happening
I am implementing first time this type of functionality so got stuck to resolve the problem
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 13
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
cell.btnAddToCart.tag = indexPath.row
cell.btnAddToCart.addTarget(self, action: #selector(addToCartDell(sender:)), for: .touchUpInside)
return cell
}
This function is used for hide and show Add To Cart button option.
#objc func addToCartDell(sender: UIButton) {
let tagVal = sender.tag
let indexPath = IndexPath(item: tagVal, section: 0)
if let cell = tblProduct.cellForRow(at: indexPath) as? ProductTableViewCell {
cell.btnAddToCart.isHidden = true
}
}
Cells are reused. You don't save the hidden state of the cell so when a cell is reused the latest state is preserved.
In Swift the most efficient and reliable solution is to save the state added to cart in the data model and use a callback closure to update the UI in cellForRow.
In the data model add a property addedToCart, it's assumed that a custom struct or class is used as data model
var addedToCart = false
In ProductTableViewCell add the callback variable and an IBAction. Connect the IBAction to the button
var callback : (()->())?
#IBAction func buttonPressed(_ sender : UIButton) {
callback?()
}
In the controller in cellForRow handle the callback, products represents the data source array
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tblCell") as! ProductTableViewCell
let product = products[indexPath.row]
cell.btnAddToCart.isHidden = product.addedToCart
cell.callback = {
product.addedToCart = true
cell.btnAddToCart.isHidden = true
}
return cell
}
No tags, no target/action, no protocols, no extra work in willDisplayCell .
This issue is turning up because we re-user same cell for displaying any further rows that was not visible yet.
you may implement this method to correct the display of any further cells
optional func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath)
Customize the cell as you want it to appear in this delegate method. This delegate method is called just before the cell is displayed, so you can do any customization here and it will turn up in the UI as per your customization.
If we go deep in to implementation.
There must be a model that keeps the state addedToCart in this model on basis of the button tapped in a particular row and use this same model's addedToCart (model.addedToCart) to show hide the button in delegate method.
I have a collectionView used for scrolling between pages, inside of one of these full page cells I have another collectionView with cells. How do I perform a segue when one of the cells inside of the inner most collectionView is tapped.
You will need a delegate on the cells with collection view, that will need to be notified when a particular cell is selected:
protocol CollectionCellDelegate: class {
func selectedItem()
}
class CollectionCell: UITableViewCell {
weak var delegate: CollectionCellDelegate?
// ...
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
self.delegate?.selectedItem()
}
}
And in the TableViewController you will have to implement that delegate to perform segue from it (you have to perform segue from UIViewController subclass, but the UITableViewCell is not subclassing it, that's why you need the delegate pattern).
class TableViewController: UITableViewController, CollectionCellDelegate {
// ...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
// set its delegate to self
cell.delegate = self
return cell
}
func selectedItem() {
// here you can perform segue
performSegue(withIdentifier: "mySegue", sender: self)
}
}
I haven't passed any argument to the delegate, but you can of course use arguments to pass any information that you need for the segue (e.g., the id of the collection cell that was selected, etc.).
When you tap on an item in collectionView, the following delegate method will be called (if you wired up everything properly) func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)... notice that the first param is collectionView itself.
Depending on how you set it up...
if you have two collectionViews within one UIViewController then you can do..
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
if collectionView == self.innerCollectionView {
// performSegue...
}
}
if you have two view controllers, one for outer and another for inner.. then you can create use delegate pattern to let the outer know which item got selected, and segue using that info.
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
I am working on an app where I'm stuck in a limbo between collection view cells and the contained table views within them.
My first collection view cell contains a table view with table view cells.
Each table view cell contain saved data and by selecting one cell two things should happen.
The collection view cell should change index to current +1
The table view cells data (in this case title and date) should be passed onto the new collection view cells header property.
One other aspect is that the table views are stored in a container view class. I'm not sure if this matter or not, but its one extra layer to pass the variables through.
So far this is where I get stuck
tableViewDidselectCode
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let header = LoadHeaderView()
let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as SavedTableViewCell
header.titleString = cell.taskLabel.text!
header.descriptionString = cell.clientLabel.text!
}
How do I pass this to
self -> ContainerView -> collectionCell[0] -> CollectionView -> collectionCell[1] -> tableView -> header?
Your table view has a dependency on the parent collection view cell. That means you need to pass a reference of the collection view cell to the table view on instantiation. I'd make a protocol.
protocol myTableViewCellDelegate {
func updateMyCollectionViewCell
}
extension myCollectionViewCell: myTableViewCellDelegate {
func updateMyCollectionViewCell {
// update whatever you need to here
}
}
extension myCollectionTableViewDelegate: UITableViewDelegate {
// ...
func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
// instantiate table view
// pass self to tableview *as weak reference*
myTableView.customDelegate = self
}
//...
}
extension myTableViewDelegate: UITableViewDelegate {
//...
func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
// instantiate table view cell
// assuming you're not using custom cell class
tableCell.updateMyCollectionViewCell()
// if you are using custom cell class then the tableViewDelegate
// will need to pass collectionView reference on to the cells
}
}
Hope that helps!
I think you can use a blocks to send information and any other actions.
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html