I would like my tableView to only react to double taps and not at all to single taps. I am currently using the following code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTap))
tapGesture.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGesture)
let doubleTapGesture = UITapGestureRecognizer()
doubleTapGesture.numberOfTapsRequired = 2
view.addGestureRecognizer(doubleTapGesture)
tapGesture.require(toFail: doubleTapGesture)
// implement what to do
if userInfo[indexPath.row].identifier == "username" {
editUsername()
}
}
func singleTap() {
// DO NOTHING
}
So basically I have been trying to "redirect" the single tap to a function that does nothing. However, I find that (in the simulator), the tableView sometimes reacts to the single tap, sometimes not. Any help to solve this issue is highly appreciated!
To achieve your goal:
Add tap gesture recognizer on your table view, do not forget to set numberOfTapsRequired = 2
Do not implement didSelectRowAtIndexPath method
To prevent table view cells from changing their background color after single tap, set in interface builder, Attributes Inspector tab, table view "selection" attribute to "No selection" or table view cell "selection" attribute to "None".
If you want to get indexpath of cell being doubletapped, in your gesture recognizer handler method get tap location in tap.view and use indexPathForRowAtPoint method of tableView:
let tapLocationPoint = tap.location(in: tap.view)
let tappedCellIndexPath = tableView.indexPathForRow(at: tapLocationPoint)
I have 2 VCs, one of them is called HomeVC the other is DetailVC. I have a table view on HomeVC which displays cells with a label and a button. DetailVC just has a label. I am displaying an array of strings on the table view and when the button on the cell is clicked i want to carry the text in the label to the DetailVC's label.
Now i can easily do this with either didSelectRowAt method or using indexPathForSelectedRow in prepare segue method. But both cases requires me to tap on the cell itself but not the button.
I am just a beginner in swift. But to explain this there shouldn't be need for much code. So if you can, please explain with detail.
Thanks in advance.
In cellForRowAt add target to button i.e
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: Selector("buttonAction:"), for: .touchUpInside)
// other cell element setup
return cell
}
And at button action get the item from array using button tag i.e
func buttonAction(sender: UIButton) {
let data = tableArray[sender.tag]
// logic to pass present detailVC
}
Hope this will work!!
If you are using collection view you can use the following
// Set The Click Action On Button
cell.bProfileImage.addTarget(self, action: #selector(connected(sender:)), for:
.touchUpInside)
cell.bProfileImage.tag = indexPath.row
Then in your function
// Function For TouchUpInside For Cell
#objc func connected(sender: UIButton) {
let data = individualChatsListArray[sender.tag]
print(data.name)
}
In my Swift code, I have a UICollectionViewCell with 3 buttons (all three have IBActions). From my UICollectionViewController I now want to "catch" the individual button taps.
I've followed this StackOverflow question and I can catch the UICollectionViewCell's touch-up inside up in my CollectionViewController with adding this line to the viewDidLoad
gestureRecognizer.cancelsTouchesInView = false
and with this function
func handleTapForCell(recognizer: UITapGestureRecognizer){
//I can break in here
}
But the piece missing now is how can I figure out which of the three buttons have been tapped? I have set different tags on the buttons but I have not found any place on the gestureRecognizer dealing with these tags.
Any ideas?
I think, you don't need to add Gesture on cell to get a button action of a tableviewCell. This code may help you:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Your tableviewCell code here
//set tag of cell button
cell.button1.tag = 1
cell.button2.tag = 2
cell.button3.tag = 3
//add action of your cell button
cell.button1.addTarget(self, action: Selector("cellButtonTapped:event:"), forControlEvents: .TouchUpInside)
cell.button2.addTarget(self, action: Selector("cellButtonTapped:event:"), forControlEvents: .TouchUpInside)
cell.button3.addTarget(self, action: Selector("cellButtonTapped:event:"), forControlEvents: .TouchUpInside)
// return cell
}
func cellButtonTapped(sender:UIButton, event:AnyObject){
let touches: NSSet = event.allTouches()!
let touch = touches.anyObject()
let currentTouchPosition: CGPoint = (touch?.locationInView(YOUR_TABLEVIEW_INSTANCE))!
if let indexPath: NSIndexPath = self.YOUR_TABLEVIEW_INSTANCE.indexPathForRowAtPoint(currentTouchPosition)!{
if sender.tag == 1{
//cell first button tap
}else sender.tag == 2{
//cell second button tap
}
else sender.tag == 3{
//cell 3rd button tap
}
}
}
You can follow the protocol/delegate paradigm.
What you need to do is define a protocol in Custom cell. Then make the viewcontroller subscribe to the cell delegate.
Implement the IBActions inside the custom cell class. Call the delegate methods in the IBActions of the buttons. viewcontroller who is delegating for the cell will receive the callbacks for button taps inside the cell.
I have a button on a tableview cell.
When its pressed, at the moment all I want is to add a different image.
When its already in the function its already been pressed, so assigning an control event must be wrong. This doesn't work :
#IBAction func HeartPressed(sender: AnyObject) {
var heartImage : UIImage = UIImage(named: "HeartPink.png")!
sender.setImage(heartImage, forState: UIControlState.Selected)
}
Any idea how to get an image set on a button in a tableviewcell ?
It happen because button is overload one by one and in cell always last cell button change effect that you doing ..
so please do in this minor change in this function
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
in this function set button tag of all ..
button.tag = indexPath.row
and try again ..
I am creating a Detail disclosure button which is getting populated using an array.... However the accessoryButtonTappedForRowWithIndexPath: function is not being called in my class. It is a TableviewDelegate and TableviewDatasource delegate.
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
NSLog(#"reaching accessoryButtonTappedForRowWithIndexPath:");
[self performSegueWithIdentifier:#"modaltodetails" sender:[self.eventsTable cellForRowAtIndexPath:indexPath]];
}
The NSLog isnt printing to console which leads me to believe the function isnt being called... This is of course when I select on a cell. A screenshot below shows how I have my cell setup.
The doc says that the method tableView:accessoryButtonTappedForRowWithIndexPath: is not called when an accessory view is set for the row at indexPath. The method is only called when the accessoryView property is nil and when one uses and set the accessoryType property to display a built-in accessory view.
As I understand it, accessoryView and accessoryType are mutually exclusive. When using accessoryType, the system will call tableView:accessoryButtonTappedForRowWithIndexPath: as expected, but you have to handle the other case by yourself.
The way Apple does this is shown in the Accessory sample project of the SDK. In the cellForRowAtIndexPath method of the dataSource delegate, they set a target/action to a custom accessory button. Since one can't pass the indexPath to the action, they call an auxiliary method to retrieve the corresponding indexPath and they pass the result to the delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
...
// set the button's target to this table view controller so we can interpret touch events and map that to a NSIndexSet
[button addTarget:self action:#selector(checkButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
...
cell.accessoryView = button;
return cell;
}
- (void)checkButtonTapped:(id)sender event:(id)event{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil){
[self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
For some reason, your setup seems to fall in the accessoryView case. Have you tried to set the accessoryType with code instead of using the Interface Builder ?
This seems very relevant...
Disclosure indicator: When this element is present, users know they can tap anywhere in the row to see the next level in the hierarchy or the choices associated with the list item. Use a disclosure indicator in a row when selecting the row results in the display of another list. Don’t use a disclosure indicator to reveal detailed information about the list item; instead, use a detail disclosure button for this purpose.
Detail disclosure button: Users tap this element to see detailed information about the list item. (Note that you can use this element in views other than table views, to reveal additional details about something; see “Detail Disclosure Buttons” for more information.) In a table view, use a detail disclosure button in a row to display details about the list item. Note that the detail disclosure button, unlike the disclosure indicator, can perform an action that is separate from the selection of the row. For example, in Phone Favorites, tapping the row initiates a call to the contact; tapping the detail disclosure button in the row reveals more information about the contact.
Converting the top answer to Swift 3:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
let unseletcedImage = UIImage(named: "<imagename>")
let seletcedImage = UIImage(named: "<imagename>")
let button = UIButton(type: .custom)
// match the button's size with the image size
let frame = CGRect(x: CGFloat(0.0), y: CGFloat(0.0), width: CGFloat((unseletcedImage?.size.width)!), height: CGFloat((unseletcedImage?.size.height)!))
button.frame = frame
button.setBackgroundImage(unseletcedImage, for: .normal)
button.setBackgroundImage(seletcedImage, for: .selected)
cell?.accessoryView = button
let action = #selector(checkButtonTapped(sender:event:))
(cell?.accessoryView as? UIButton)?.addTarget(self, action: action, for: .touchUpInside)
....
return cell!
}
#objc func checkButtonTapped(sender: UIButton?, event: UIEvent) {
let touches = event.allTouches
let touch = touches!.first
guard let touchPosition = touch?.location(in: self.tableView) else {
return
}
if let indexPath = tableView.indexPathForRow(at: touchPosition) {
tableView(self.tableView, accessoryButtonTappedForRowWith: indexPath)
}
}
According to UITableViewCellAccessoryType's documentation it is expected behaviour:
typedef enum : NSInteger {
UITableViewCellAccessoryNone,
UITableViewCellAccessoryDisclosureIndicator,
UITableViewCellAccessoryDetailDisclosureButton,
UITableViewCellAccessoryCheckmark,
UITableViewCellAccessoryDetailButton } UITableViewCellAccessoryType;
Constants UITableViewCellAccessoryNone The cell does not have any
accessory view. This is the default value.
UITableViewCellAccessoryDisclosureIndicator The cell has an accessory
control shaped like a chevron. This control indicates that tapping the
cell triggers a push action. The control does not track touches.
UITableViewCellAccessoryDetailDisclosureButton The cell has an info
button and a chevron image as content. This control indicates that
tapping the cell allows the user to configure the cell’s contents. The
control tracks touches.
UITableViewCellAccessoryCheckmark The cell has a check mark on its
right side. This control does not track touches. The delegate of the
table view can manage check marks in a section of rows (possibly
limiting the check mark to one row of the section) in its
tableView:didSelectRowAtIndexPath: method.
UITableViewCellAccessoryDetailButton The cell has an info button
without a chevron. This control indicates that tapping the cell
displays additional information about the cell’s contents. The control
tracks touches.
Did you just click on the cell to select it or did you actually click on the accessory button indicator on the cell? It isn't clear from your question.
accessoryButtonTappedForRowWithIndexPath is applicable when you click on the button icon within the cell and not when you select the cell.
drew is spot on.
If you change to 'DetailDisclosure' in Storyboard, then the method will fire. (xCode 4.6 DP3)
My tableView's accessoryButtonTappedForRowWithIndexPath(…) was not being called when the Detail disclosure button was hit. I eventually found that the problem was simply that tableView delegate had not been set:
self.tableView.delegate = self
This is another way to handle the disclosure button using the storyboard. You should click the table view cell and goto the Connections Inspector. There is a section called Triggered Segues, and you can drag from the selection line to the UIViewController that you want to segue to. The segue will happen automatically and you can capture prepareForSegue to capture the notification when it does.
The function accessoryButtonTappedForRowWithIndexPath never gets called for the disclosure button. It only gets called for the detailed disclosure button.
Tested on Swift 5.7 / iOS 16 (obviously would work in much earlier versions)
This solution makes use of nested functions to keep handlers and setup local to limited use cases.
extension MyViewController : UITableViewDelegate {
#objc func findRowOfTappedButton(sender: UIButton?, event: UIEvent) {
let touches = event.allTouches
let touch = touches!.first
guard let touchPosition = touch?.location(in: self.tableView) else { return }
if let indexPath = tableView.indexPathForRow(at: touchPosition) {
tableView(self.tableView, accessoryButtonTappedForRowWith: indexPath)
}
}
// Note: By implementing the following handler as appropriate delegate method,
// the scheme will still work if accessoryType changes to OS-provided type.
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
tableView.delegate?.tableView!(tableView, didSelectRowAt: indexPath)
// Do whatever when button at this row is pressed.
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Do whatever when row is selected w/o button press.
}
}
extension MyViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myContent.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
func addAccessoryButtonToCell(_ cell : UITableViewCell) {
let image = UIImage(systemName: "paintpalette")
let button = UIButton(type: .custom)
button.frame = CGRect(x:0, y:0, width: 20, height: 20)
button.setImage(image, for: .normal)
button.addTarget(self, action: #selector(findRowOfTappedButton(sender:event:)), for: .touchUpInside)
cell.accessoryView = button
}
let cell = tableView.dequeueReusableCell(withIdentifier: "MyReusableCell", for: indexPath)
addAccessoryButtonToCell()
content.attributedText = NSAttributedString(string: myContent[indexPath.row], attributes: myAttributes[indexPath.row])
}
}