accessoryButtonTappedForRowWithIndexPath: not getting called - ios

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])
}
}

Related

ONLY double tap on UITableView Swift 3.0

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)

Using a button on a cell to pass data in swift

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)
}

Figure out which of multiple buttons in UICollectionView cell was tapped

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.

Implementing accessoryButtonTappedForRowWithIndexPath: in Swift 2

I'm attempting to implement accessoryButtonTappedForRowWithIndexPath: in Swift 2 on a UITableViewController.
As I explain below, I think I'm missing something in when I create the disclosureIndicator, but I don't know what. It gets drawn from code, but my target action doesn't get called. UGH!
To do this programmatically, my understanding is I need to add the detailDisclosure indicator in cellForRowAtIndexPath before my cell is returned. I'm doing that as follows:
// Create disclosure indicator button in the cell
let disclosureIndicatorButton = UIButton(type: UIButtonType.DetailDisclosure)
disclosureIndicatorButton.addTarget(self, action: "disclosureIndicatorPressed:event:", forControlEvents: UIControlEvents.TouchUpInside)
customCell.accessoryType = .DisclosureIndicator
In my code, the detailDisclosure chevron gets drawn, but the target action method I assigned to it doesn't get called.
Then I need to create a handler for the button when it's pressed:
func disclosureIndicatorPressed(sender: UIButton, event: UIControlEvents) {
print("disclosure button pressed")
// convert touches to CGPoint, then determine indexPath
// if indexPath != nil, then call accessoryButtonTappedForRowWithIndexPath
}
Finally accessoryButtonTappedForRowWithIndexPath contains code to perform the segue, which I can do. What am I missing?
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/#//apple_ref/occ/intfm/UITableViewDelegate/tableView:accessoryButtonTappedForRowWithIndexPath:
Not sure why you are adding disclosure button indicator like that in code.
What you are looking for is simply 2 step process -
Step 1 : Add correct accessoryType on cell:
cell.accessoryType = .DetailDisclosureButton
Step 2 : Take action when the button is tapped by creating the accessoryButtonTappedForRowWithIndexPath function:
override func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) {
doSomethingWithItem(indexPath.row)
}

for loop to check on cell.textlabel or cell.tag

can anyone please help me with how to access a specific cell with its tag or text label, i have a table view with a list of buttons above, those buttons filed with the thumbnail of the cell when ever the cell was clicked then also the background of the cell goes red.
when i click on the cell once again - when its red - the background goes white - default - also the thumbnail removed from the button above.
also when i click on the button the photo in it should disappear as well as the background of the cell itself, it should go white. and thats what i couldnt do .. can anyone help me with that, how to access the the cell to change its background when the cell.tag or cell. texlabel.text is given.
thanx in advance
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = myTable.dequeueReusableCellWithIdentifier("intrest") as UITableViewCell
cell.tag = indexPath.row + 1
cell.textLabel?.text = InterstArr[indexPath.row]
var img = UIImageView(frame: CGRect(x: 10, y: 3, width: 40 , height: 40))
img.image = UIImage(named:"\(InterstArr[indexPath.row]).png")
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.indentationLevel = 1
cell.indentationWidth = 45
cell.addSubview(img)
return cell
}
I did it with the help of #natuslaedo ... i just stored the value of the index path when i clicked on the row .. then in my if statements to check on the 5 buttons above and deselect them, i took the indexpath stored and accessed the cell to change its color ... i initialise the index paths variables like this var firstIP:NSIndexPath!
First of all, add the following line to your didSelectRowAtIndexPath: function:
yourTableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: UITableViewScrollPosition.None)
And target selector for your button is like as follows:
func buttonPressed(sender: UIButton!) {
// Code to remove photo from your button
// ......
// To remove background effect of cell
let indexPaths:Array<NSIndexPath> = yourTableView.indexPathsForSelectedRows() as Array<NSIndexPath>
// If you have implemented your own class which inherits from UITableViewCell, use its name while type casting and explicit typing
for indexPath in indexPaths {
let cell:UITableViewCell = yourTableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
// REMOVE BACKGROUND EFFECT HERE (using cell)
}
}
You shouldn't be trying to match on tag or text content, everything should be based on row or index path so that it correlates back to your data model.
Consider creating a custom class for your button data storage so you have an array with one entry per button and the class holds the index path of the associated cell, the image, etc. Then, when a button is clicked it's easy to update your main data model and the cell (if it's visible).
You can get the indexPath of cell in click event this way,
func buttonClick(sender:UIButton)
{
let position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
if let indexPath = self.tableView.indexPathForRowAtPoint(position)
{
println(indexPath.row)
println(indexPath.section)
}
}
Another way to match buttons in a cell with your data model is to store the indexPath of the cell with the cell. When the button is tapped, your target method which is a method on the cell, will get called and you can use the indexPath and pass it up to your data model using a delegate call.
I'd give you more details but I am not sure if that is what you are asking.

Resources