I have a button inside a tableview cell (as shown in the below image), when I click the button,
didSelectRowAt indexpath
is not being triggered, could some one please suggest how I could do this ?
Please note:
I am performing a set of actions on click of the button, in addition I would also like
didselectRowAt indexPath
to be triggered.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Table view cell has been clicked")
}
When you have multiple views in the cell they may receive the touch event and not the "cell" so didSelectRowAt will not be triggered. In that case add view.isUserInteractionEnabled = false to each view, label or button.
Put the outlet to the button inside a custom cell class. Then set tag and add selector for button in cellForItemAt. This example is for colletionView just change to suit tableView.
If you want the button in each cell this is how you have to do it. Adding a static button to a single cell won't call didSelectItemAt because your tapping a button that doesn't have reference to a reusable cells indexPath.
This way we send the button.tag tot he function so we know which cell the button is related to.
class MyClassViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
.... // Stuff
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
....// Create Cell
cell.deleteCellButton.tag = indexPath.item
cell.deleteCellButton.addTarget(self, action: #selector(MyClassViewController.deleteCellButtonTapped(_:)), for: .touchUpInside)
return cell
}
func deleteCellButtonTapped(_ sender: Any) {
... // Stuff
print("Selector called")
}
}
If you are adding button or gesture on UITableViewCell , didselectRowAt indexPath not invoked . You can add button on remaining UI UITableViewCell than clear color apply on button . User didn't show button and you can perform didselectRowAt indexPath method task . If you wanna indexpath in button method code give below.
func btnChildDropDown(sender:UIButton)
{
let cell = sender.superview?.superview as! ChildAgeTableViewCell
let indexPath = self.tblViewAddRooms.indexPath(for: cell)
}
Related
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.
I have a UICollectionViewController, which scrolls vertically(like a tableview). I created a custom UICollectionViewCell. Inside of a custom cell, there are checkmarks. I need some kind of event to click when the user clicks on a checkmark.
What I tried, was overriding:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("clicked")
}
but that only executes when the use clicks on a cell. But, asI stated above, the cell contains checkmarks...I need to find out when each individual check mark is clicked.
Is this possible?
You can add an IBAction to the UICollectionViewCell class and handle the tap from that IBAction, if you need it to change something on the Parent view controller you have a couple of options, you can use a delegate or pass the controller to the cell.
On the parent view controller :
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.controller = self
return cell
}
On the UICollectionViewCell class:
var controller: ParentViewController?
#IBAction func checkmarkPressed(sender: UIButton) {
print("checkmarkPressed")
controller.someFunction()
}
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 am attempting to programmatically create a button in each cell of a UICollectionView; however, only the first button is visible. I have tried adding print statements to see what subviews my cells have and the button is present but it is not appearing on the screen.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath)
// Configure the cell
let button = UIButton(frame: cell.frame)
button.addTarget(self, action: #selector(cellClicked), for: UIControlEvents.touchUpInside)
button.backgroundColor = UIColor.red
button.tag = indexPath.row
cell.addSubview(button)
print(cell.subviews)
return cell
}
Also, I added a print statement when clicking the buttons and only the first button shows up and prints out 0.
#IBAction func cellClicked(sender: UIButton) {
print(sender.tag)
}
Here is a screenshot of the collection view, there should be two buttons in the picture but only one appears
Any help is much appreciated.
It's very bad to add button in data source, because when cell reused, new buttons will be created. If you're using Interface Builder, please add button directly. And you can adjust their properties. You can also define a custom cell, and just CTRL-Drag an outlet. Or handle selection in collection view's delegate.
optional public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
Another solution is add button in cell's awakeFromNib(), this will be called only once.
My each UICollectionViewCell class have a delete button:
#IBAction func hideSingleCampaign(sender: AnyObject) {
self.removeFromSuperview()
}
Once tapped, cell disappears, and UICollectionView is left with empty space. I have no UICollectionView reference from cell class so I can't use collectionView.reloadData()
How am I supposed to shift other cells up?
You need to remove the object from the array that you are using with your CollectionViewDataSource method instead of deleting cell, So first of all declare action of Button inside CollectionViewCell, so that add action of button in the cellForItemAtIndexPath like this.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath:NSIndexPath)->UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as CollectionCell
cell.delBtn.tag = indexPath.item
cell.delBtn.addTarget(self, action: #selector(self.onDeletTapped(_:)),
forControlEvents: .TouchUpInside)
return cell
}
Add this method onDeletTapped on your ViewController
func onDeletTapped(sender: UIButton) {
let index = sender.tag
yourArray.removeAtIndex(index)
self.collectinView.reloadData()
}