ONLY double tap on UITableView Swift 3.0 - ios

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)

Related

Detecting Tap Events for UILabel inside UITableViewCell using UITapGestureRecognizer

I have a UITableViewDataSource with the following
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: OutletDetails.cellIdentifier) as! OutletDetails
cell.selectionStyle = .none
cell.isUserInteractionEnabled = true
cell.location = "Some location will be here"
let tap = UITapGestureRecognizer(target: self, action: #selector(locationClicked))
tap.cancelsTouchesInView = false
tap.numberOfTapsRequired = 1
cell.location.addGestureRecognizer(tap)
}
where cell.location is a UILabel object. What I'm trying to do here is to detect tap events on the UILabel. I looked all over the Internet and everyone is suggesting this method, however, this code is not working in my case. The method locationClicked is not being called at all. Can anyone tell me what's wrong with my code?
Edit
One more thing, is it a good idea to do it this way memory-wise? I mean if we have a long list, then many UIGestureRecognizer objects will be generated for each cell. This is because the method will be called a lot while scrolling the items.
Add tap gesture to the object and enable its user interaction. Yes you can take button as well.
//Adding tap gesture
let cellNameTapped = UITapGestureRecognizer(target: self, action: #selector(nameTapped))
nameLabel.isUserInteractionEnabled = true// UILabel made available for touch interaction
nameLabel.addGestureRecognizer(cellNameTapped) //gesture added
//Method called on touch of nameLabel
#objc func nameTapped(tapGestureRecognizer: UITapGestureRecognizer){
//print(tapGestureRecognizer.view)
}
Since you're dequeuing a cell, you will need to somehow get a reference to the UITapGestureRecognizer and either remove it or reuse it. Otherwise every time you reuse a cell, you will be laying another recognizer onto the one that is already on the cell. If you're subclassing the UITableViewCell you can just add the recognizer as a property.
However, using the code you posted I'm suggesting you use a UIButton and add a tag so you can get a reference to it later. You can set the bounds of the button equal to the bounds of the label. Try something like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: OutletDetails.cellIdentifier) as! OutletDetails
cell.selectionStyle = .none
cell.isUserInteractionEnabled = true
cell.location = "Some location will be here"
// If we don't already have a button on our cell, create one and set the tag
if cell.viewWithTag(103) as? UIButton == nil {
let newButton = UIButton(frame: cell.location.bounds)
newButton.tag = 103
newButton.addTarget(target: self, action: #selector(locationClicked), for: .touchUpInside)
}
}

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

iOS 9: Gesture Recognizer was setup in a storyboard/xib to be added to more than one view (not working)

Using iOS 9 and facing a problem with a UITapGestureRecognizer. I have a ViewController-A with a UITableView. I have added a tableViewCell which has a textLabel. I want to implement tap on the textLabel. So if I tap on textLabel -- it should print on Console or do anything else
Issue: TapRecogniser is not working. Getting the below error:
Following is what I have done:
1) Added a `UITapGestureRecognizer' on the textLabel (From StoryBoard). Enabled User Interaction for the textLabel (the error even now)
2) Following is the IBAction:
#IBAction func nameTap(sender: UITapGestureRecognizer) {
print("a")
}
3) CellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! ThirdViewCell!
cell.nameLabel?.text = "XYZ"
let nameTapRecognizer = UITapGestureRecognizer(target: self, action: Selector("nameTap:"))
nameTapRecognizer.cancelsTouchesInView = false
cell.nameLabel?.addGestureRecognizer(nameTapRecognizer)
return cell
}
P.S:
1) This was working in iOS 8. I have checked..There are no duplicates (there is only one tap recognizer in the entire file and its linked to the textLabel)
2) I don't want to use didSelectRowAtIndexPath method as I need to implement TapGestureRecognizer for more textLabels within the tableViewCell.
are you see the error console Label, and the property as UserInteractionEnabled = NO; see the screen shot
try this
let nameTapRecognizer = UITapGestureRecognizer(target: self, action: Selector("nameTap:"))
nameTapRecognizer.cancelsTouchesInView = false
cell.nameLabel?.tag = indexPath.row // add this
nameTapRecognizer.numberOfTapsRequired = 1 // add this
nameTapRecognizer.delegate =self
cell.nameLabel?.userInteractionEnabled = true // add this
cell.nameLabel?.addGestureRecognizer(nameTapRecognizer)
// method
func nameTap(gesture: UITapGestureRecognizer) {
let indexPath = NSIndexPath(forRow: gesture.view!.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell
// Do whatever you want.
}

accessoryButtonTappedForRowWithIndexPath: not getting called

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

Resources