How to detect tap on UITextField? - ios

I have a UITextField that has User Interaction Disabled. So if you tap on this text field, nothing happens. Normally to check if a text field was tapped Id try the delegate methods, but I cannot because user interaction is disabled. Is there any way I can check if the text field was tapped/touched? I change another element to hidden = no; when it is tapped so I was wondering if its even possible enabling user interaction.

Best option is to turn on User Interaction and disable edit action using delegate method.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
return NO;
}
You can call your method inside that function to detect tap.

Maybe, you can add UITapGestureRecognizer in the superview, detect if the touch is inside the frame, and then do something.
Detect touch if it is inside the frame of the super view
Create UITapGestureRecognizer and add that to the UITextField's super view.
Implement the target selector and check if the gesture's state has ended.
Call your method.
Objective-C
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didRecognizeTapGesture:)];
[self.textField.superview addGestureRecognizer:tapGesture];
- (void) didRecognizeTapGesture:(UITapGestureRecognizer*) gesture {
CGPoint point = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateEnded) {
if (CGRectContainsPoint(self.textField.frame, point)) {
[self doSomething];
}
}
}
Swift 3
func viewDidLoad() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didRecognizeTapGesture(_:)))
textField.superView?.addGestureRecognizer(tapGesture)
}
private dynamic func didRecognizeTapGesture(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: gesture.view)
guard gesture.state == .ended, textField.frame.contains(point) else { return }
//doSomething()
}

Related

Can you have multiple recognisers for the same gesture in Swift? [duplicate]

I have written my own function to scroll text fields up when the keyboard shows up. In order to dismiss the keyboard by tapping away from the text field, I've created a UITapGestureRecognizer that takes care of resigning first responder on the text field when tapping away.
Now I've also created an autocomplete for the textfield that creates a UITableView just below the text field and populates it with items as the user enters text.
However, when selecting one of the entries in the auto completed table, didSelectRowAtIndexPath does not get called. Instead, it seems that the tap gesture recognizer is getting called and just resigns first responder.
I'm guessing there's some way to tell the tap gesture recognizer to keep passing the tap message on down to the UITableView, but I can't figure out what it is. Any help would be very appreciated.
Ok, finally found it after some searching through gesture recognizer docs.
The solution was to implement UIGestureRecognizerDelegate and add the following:
#pragma mark UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isDescendantOfView:autocompleteTableView]) {
// Don't let selections of auto-complete entries fire the
// gesture recognizer
return NO;
}
return YES;
}
That took care of it. Hopefully this will help others as well.
The easiest way to solve this problem is to:
UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(tap:)];
[tapRec setCancelsTouchesInView:NO];
This lets the UIGestureRecognizer recognize the tap and also pass the touch to the next responder. An unintended consequence of this method is if you have a UITableViewCell on-screen that pushes another view controller. If the user taps the row to dismiss the keyboard, both the keyboard and the push will be recognized. I doubt this is what you intend, but this method is adequate for many situations.
Also, expanding on Robert's answer, if you have a pointer to the tableview in question, then you can directly compare its class instead of having to convert to a string and hope Apple doesn't change the nomenclature:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
if([touch.view class] == tableview.class){
return //YES/NO
}
return //YES/NO
}
Remember, you must also declare the UIGestureRecognizer to have a delegate with this code in it.
Set cancelsTouchesInView of your recognizer to false. Otherwise, it "consumes" the touch for itself, and does not pass it on to the table view. That's why the selection event never happens.
for example in swift
let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)
And for Swift (based on answer from #Jason):
class MyAwesomeClass: UIViewController, UIGestureRecognizerDelegate
private var tap: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
self.tap = UITapGestureRecognizer(target: self, action: "viewTapped:")
self.tap.delegate = self
self.view.addGestureRecognizer(self.tap)
}
// UIGestureRecognizerDelegate method
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view?.isDescendantOfView(self.tableView) == true {
return false
}
return true
}
I may have a better solution to add a tap gesture over a table view but allowing cell selection at the same time:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
let location = touch.locationInView(tableView)
return (tableView.indexPathForRowAtPoint(location) == nil)
}
return true
}
I just look for a cell at the point of the screen where the user is tapping. If no index path is found then I let the gesture receive the touch otherwise I cancel it. For me it works great.
I think there is no need to write blocks of codes just simply set
cancelsTouchesInView to false for your gesture object ,
by default it's true and you just have to set it false .
If you are using UITapGesture object in your code and also using UIScrollView(tableview , collectionview)then set this property false
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
A similar solution is to implement gestureRecognizer:shouldReceiveTouch: using the view's class to determine what action to take. This approach has the advantage of not masking taps in the region directly surrounding the table (these area's views still descend from the UITableView instances, but they do not represent cells).
This also has a bonus that it works with multiple tables on a single view (without adding extra code).
Caveat: there is an assumption that Apple won't change the classname.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return ![NSStringFromClass([touch.view class]) isEqualToString:#"UITableViewCellContentView"];
}
For Swift 4.2 the solution was to implement UIGestureRecognizerDelegate and add the following:
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.isDescendant(of: tblView) {
return false
}
return true
}
}
when you click on table view this delegate method returns false and didSelectRowAtIndexPath method of table view working properly.
I had a different situation where I wanted the touch gesture function to be called only when the user tapped outside of the table view. If the user tapped inside the table view, then the touch gesture function shouldn't be called. Additionally, If the touch gesture function is called, it should still pass the touch event to the view that was tapped on rather than consuming it.
The resulting code is a combination of Abdulrahman Masoud's answer, and Nikolaj Nielsen's answer.
extension MyViewController: UIGestureRecognizerDelegate {
func addGestureRecognizer() {
let tapOnScreen = UITapGestureRecognizer(target: self,
action: #selector(functionToCallWhenUserTapsOutsideOfTableView))
// stop the gesture recognizer from "consuming" the touch event,
// so that the touch event can reach other buttons on view.
tapOnScreen.cancelsTouchesInView = false
tapOnScreen.delegate = self
self.view.addGestureRecognizer(tapOnScreen)
}
// if your tap event is on the menu, don't run the touch event.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: self.tableView) == true {
return false
}
return true
}
#objc func functionToCallWhenUserTapsOutsideOfTableView() {
print("user tapped outside table view")
}
}
And in the MyViewController class, the class which has the UITableView, in the onViewDidLoad(), I made sure to call addGestureRecognizer():
class MyViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
...
self.addGestureRecognizer()
...
}
...
}
Just set cancels touches in view to false for any gesture underneath the (table/collection)View:
- Interfacebuilder
- Code
<#gestureRecognizer#>.cancelsTouchesInView = false
Swift 5, May 2020.
I have a textField and a tableView that becomes visible when I enter text.
Initial state
So I want 2 different events when I tap tableViewCell or something else.
Keyboard and tableView are being shown
First we add tapGestureRecognizer.
tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
tap.delegate = self
view.addGestureRecognizer(tap)
#objc func viewTapped() {
view.endEditing(true)
}
Then we add the following check into UIGestureRecognizerDelegate:
extension StadtViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: self.tableView) == true {
return false
} else {
view.endEditing(true)
return true
}
}
}
If I want to hide keyboard first, the tableView remains visible and responsive to my taps.
enter image description here
Simple solution is using UIControl instances in UITableViewCell to get touches. You can add any views with userInteractionEnables == NO to UIControl to get taps.
For CollectionView in Swift 5:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
let location = touch.location(in: mCollectionView)
return (mCollectionView.indexPathForItem(at: location) == nil)
}
return true
}
While it's late and many people find that the above suggestions work fine, I could not get Jason's or TMilligan's methods to work.
I have a Static tableView with multiple cells containing textFields that receive Number inputs using only the Number Keyboard. This was ideal for me:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if(![touch.view isKindOfClass:[UITableViewCell class]]){
[self.firstTF resignFirstResponder];
[self.secondTF resignFirstResponder];
[self.thirdTF resignFirstResponder];
[self.fourthTF resignFirstResponder];
NSLog(#"Touches Work ");
return NO;
}
return YES;
}
Ensure that you have implemented this <UIGestureRecognizerDelegate> in your .h file.
This line ![touch.view isKindOfClass:[UITableViewCell class]] checks whether a tableViewCell was tapped and dismisses any active keyboard.
Solution for Swift, works in 2021. For this solution you don't have to have the reference(s) to the Table View(s).
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
//return !(touch.view is UITableViewCell) <-- doesn't work, the type of the touched class is not UITableViewCell anymore
var v = touch.view
while v != nil {
if v is UITableView { return false }
v = v?.superview
}
return true
}
Usage:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YOUR_METHOD))
gestureRecognizer.delegate = self
YOUR_VIEW.addGestureRecognizer(gestureRecognizer)
Here is my solution, which ties the recognizer's shouldReceiveTouch directly to whether the keyboard is showing.
In your tap gesture recognizer delegate:
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([PFXKeyboardStateListener sharedInstance].visible) {
return YES;
}
return NO;
}
And the PFXKeyboardStateListener.h:
#interface PFXKeyboardStateListener : NSObject
{
BOOL _isVisible;
}
+ (PFXKeyboardStateListener *)sharedInstance;
#property (nonatomic, readonly, getter=isVisible) BOOL visible;
#end
And the PFXKeyboardStateListener.m:
static PFXKeyboardStateListener *sharedInstance;
#implementation PFXKeyboardStateListener
+ (PFXKeyboardStateListener *)sharedInstance
{
return sharedInstance;
}
+ (void)load
{
#autoreleasepool {
sharedInstance = [[self alloc] init];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)isVisible
{
return _isVisible;
}
- (void)didShow
{
_isVisible = YES;
}
- (void)didHide
{
_isVisible = NO;
}
- (id)init
{
if ((self = [super init])) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(didShow) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:#selector(didHide) name:UIKeyboardWillHideNotification object:nil];
}
return self;
}
#end
You may want to update the singleton pattern of the keyboard listener, I haven't gotten to it yet. Hope this works for everyone else as well as it works for me. ^^
Implement this method for delegate of UIGestureRecognizer:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
UIView *superview = touch.view;
do {
superview = superview.superview;
if ([superview isKindOfClass:[UITableViewCell class]])
return NO;
} while (superview && ![superview isKindOfClass:[UITableView class]]);
return superview != nil;
}
My case was different including a uisearchbar and uitableview on self.view. I wanted to dismiss uisearchbar keyboard by touching on the view.
var tapGestureRecognizer:UITapGestureRecognizer?
override func viewWillAppear(animated: Bool) {
tapGestureRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
}
On UISearchBar Delegate Methods:
func searchBarShouldBeginEditing(searchBar: UISearchBar) -> Bool {
view.addGestureRecognizer(tapGestureRecognizer!)
return true
}
func searchBarShouldEndEditing(searchBar: UISearchBar) -> Bool {
view.removeGestureRecognizer(tapGestureRecognizer!)
return true
}
When user touches on self.view:
func handleTap(recognizer: UITapGestureRecognizer) {
sampleSearchBar.endEditing(true)
sampleSearchBar.resignFirstResponder()
}
In swift you can use this inside
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if CheckTheTime() == true {
// do something
}else{
}
}
func CheckTheTime() -> Bool{
return true
}
This is my solution based on above answers...
It's worked for me...
//Create tap gesture for menu transparent view
UITapGestureRecognizer *rightTableTransparentViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(rightTableTransparentViewTapMethod:)];
[rightTableTransparentViewTap setCancelsTouchesInView:NO];
[_rightTableTransparentView addGestureRecognizer:rightTableTransparentViewTap];
- (void)rightTableTransparentViewTapMethod:(UITapGestureRecognizer *)recognizer {
//Write your code here
}
ISSUE:
In my case, the issue was that I originally placed a button in each collectionView cell and set the constraints to fill the cell, so that when the cell was clicked it would click the button, however the buttons function was empty so nothing was appearing to be happening.
FIX:
I fixed this by removing the button from the collection view cell.

uilongpressgesturerecognizer fire only once

I want to add a gesture that will only fire if a person has been pressing for a second or so. Not a tap but a long press. If I use uilongpressgesturerecognizer it keeps firing until I release my finger. How can I get around this.
Set minimumPressDuration when you create and add gesture as below:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self action:#selector(handleLongPress:)];
longPress.minimumPressDuration = 1.0;
Write your code in UIGestureRecognizerStateEnded state as below:
-(void)handleLongPress:(UILongPressGestureRecognizer *)Gesture{
if (Gesture.state == UIGestureRecognizerStateEnded) {
//Do any thing after long press ended,which will be 1.0 second as set above
}
else if (Gesture.state == UIGestureRecognizerStateBegan){
}
}
Swift 5
Declare a UILongPressGestureRecognizer:
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(gestureAction(gesture:)))
Set its .minimumPressDuration to 1.0 or any interval you want.
Set the recognizers .delegate to your ViewController and add it to your view using .addGestureRecognizer().
Use the following function to handle the gesture:
#objc func gestureAction(gesture: UIGestureRecognizer) {
if let longPress = gesture as? UILongPressGestureRecognizer {
if longPress.state == UIGestureRecognizer.State.began {
} else {
}
}
}
Set value of minimumPressDuration property of UILongPressGestureRecognizer.

To know which UILongPressGestureRecognizer is triggered

I add UILongPressGestureRecognizer to several UIButton with the code :
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(btnLong:)];
[btnOne addGestureRecognizer:longPress]; //there are btnTwo, btnThree for example
And when I long press on a button the method is called:
-(void)btnLong:(UILongPressGestureRecognizer *)gestureRecognizer{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
}
}
My question is, how can I know which UILongPressGestureRecognizer is triggered, because there is no tag property for UILongPressGestureRecognizer.
Give each button a unique tag number. Then in your action method you can do:
-(void)btnLong:(UILongPressGestureRecognizer *)gestureRecognizer{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
UIView *view = gestureRecognizer.view;
if (view.tag == 1) { // first button's tag
// process 1st button
} else if (view.tag == 2) { // second button's tag
// process 2nd button
}
}
}
Another option, if you have outlets for each button you can do:
-(void)btnLong:(UILongPressGestureRecognizer *)gestureRecognizer{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
UIView *view = gestureRecognizer.view;
if (view == self.firstButton) {
// process 1st button
} else if (view == self.secondButton) {
// process 2nd button
}
}
}
where firstButton and secondButton are your button properties. And yes, using == is appropriate for checking to see if the gesture's view is one of the buttons because you do mean to compare the object pointers.
Why not put the gesture rec on the common superview? Then you can determine which UIView was long-pressed by using locationInView, then accessing the view's tag property.
I have used UIView as subview to tableview cell. And I applied UILongGesture to that. this is the code worked for me.
func handleLongPressGesture(_ longPressGestureRecognizer: UILongPressGestureRecognizer){
if longPressGestureRecognizer.state == UIGestureRecognizerState.began
{
let touchPoint = longPressGestureRecognizer.location(in: tableViewObj)
if let indexPath = tableViewObj.indexPathForRow(at: touchPoint)
{
print(indexPath.row)
}
}
}
You have index path. You can do whatever you need to do.

UITapGestureRecognizer breaks UITableView didSelectRowAtIndexPath

I have written my own function to scroll text fields up when the keyboard shows up. In order to dismiss the keyboard by tapping away from the text field, I've created a UITapGestureRecognizer that takes care of resigning first responder on the text field when tapping away.
Now I've also created an autocomplete for the textfield that creates a UITableView just below the text field and populates it with items as the user enters text.
However, when selecting one of the entries in the auto completed table, didSelectRowAtIndexPath does not get called. Instead, it seems that the tap gesture recognizer is getting called and just resigns first responder.
I'm guessing there's some way to tell the tap gesture recognizer to keep passing the tap message on down to the UITableView, but I can't figure out what it is. Any help would be very appreciated.
Ok, finally found it after some searching through gesture recognizer docs.
The solution was to implement UIGestureRecognizerDelegate and add the following:
#pragma mark UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isDescendantOfView:autocompleteTableView]) {
// Don't let selections of auto-complete entries fire the
// gesture recognizer
return NO;
}
return YES;
}
That took care of it. Hopefully this will help others as well.
The easiest way to solve this problem is to:
UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(tap:)];
[tapRec setCancelsTouchesInView:NO];
This lets the UIGestureRecognizer recognize the tap and also pass the touch to the next responder. An unintended consequence of this method is if you have a UITableViewCell on-screen that pushes another view controller. If the user taps the row to dismiss the keyboard, both the keyboard and the push will be recognized. I doubt this is what you intend, but this method is adequate for many situations.
Also, expanding on Robert's answer, if you have a pointer to the tableview in question, then you can directly compare its class instead of having to convert to a string and hope Apple doesn't change the nomenclature:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
if([touch.view class] == tableview.class){
return //YES/NO
}
return //YES/NO
}
Remember, you must also declare the UIGestureRecognizer to have a delegate with this code in it.
Set cancelsTouchesInView of your recognizer to false. Otherwise, it "consumes" the touch for itself, and does not pass it on to the table view. That's why the selection event never happens.
for example in swift
let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)
And for Swift (based on answer from #Jason):
class MyAwesomeClass: UIViewController, UIGestureRecognizerDelegate
private var tap: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
self.tap = UITapGestureRecognizer(target: self, action: "viewTapped:")
self.tap.delegate = self
self.view.addGestureRecognizer(self.tap)
}
// UIGestureRecognizerDelegate method
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view?.isDescendantOfView(self.tableView) == true {
return false
}
return true
}
I may have a better solution to add a tap gesture over a table view but allowing cell selection at the same time:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
let location = touch.locationInView(tableView)
return (tableView.indexPathForRowAtPoint(location) == nil)
}
return true
}
I just look for a cell at the point of the screen where the user is tapping. If no index path is found then I let the gesture receive the touch otherwise I cancel it. For me it works great.
I think there is no need to write blocks of codes just simply set
cancelsTouchesInView to false for your gesture object ,
by default it's true and you just have to set it false .
If you are using UITapGesture object in your code and also using UIScrollView(tableview , collectionview)then set this property false
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
A similar solution is to implement gestureRecognizer:shouldReceiveTouch: using the view's class to determine what action to take. This approach has the advantage of not masking taps in the region directly surrounding the table (these area's views still descend from the UITableView instances, but they do not represent cells).
This also has a bonus that it works with multiple tables on a single view (without adding extra code).
Caveat: there is an assumption that Apple won't change the classname.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return ![NSStringFromClass([touch.view class]) isEqualToString:#"UITableViewCellContentView"];
}
For Swift 4.2 the solution was to implement UIGestureRecognizerDelegate and add the following:
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.isDescendant(of: tblView) {
return false
}
return true
}
}
when you click on table view this delegate method returns false and didSelectRowAtIndexPath method of table view working properly.
I had a different situation where I wanted the touch gesture function to be called only when the user tapped outside of the table view. If the user tapped inside the table view, then the touch gesture function shouldn't be called. Additionally, If the touch gesture function is called, it should still pass the touch event to the view that was tapped on rather than consuming it.
The resulting code is a combination of Abdulrahman Masoud's answer, and Nikolaj Nielsen's answer.
extension MyViewController: UIGestureRecognizerDelegate {
func addGestureRecognizer() {
let tapOnScreen = UITapGestureRecognizer(target: self,
action: #selector(functionToCallWhenUserTapsOutsideOfTableView))
// stop the gesture recognizer from "consuming" the touch event,
// so that the touch event can reach other buttons on view.
tapOnScreen.cancelsTouchesInView = false
tapOnScreen.delegate = self
self.view.addGestureRecognizer(tapOnScreen)
}
// if your tap event is on the menu, don't run the touch event.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: self.tableView) == true {
return false
}
return true
}
#objc func functionToCallWhenUserTapsOutsideOfTableView() {
print("user tapped outside table view")
}
}
And in the MyViewController class, the class which has the UITableView, in the onViewDidLoad(), I made sure to call addGestureRecognizer():
class MyViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
...
self.addGestureRecognizer()
...
}
...
}
Just set cancels touches in view to false for any gesture underneath the (table/collection)View:
- Interfacebuilder
- Code
<#gestureRecognizer#>.cancelsTouchesInView = false
Swift 5, May 2020.
I have a textField and a tableView that becomes visible when I enter text.
Initial state
So I want 2 different events when I tap tableViewCell or something else.
Keyboard and tableView are being shown
First we add tapGestureRecognizer.
tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
tap.delegate = self
view.addGestureRecognizer(tap)
#objc func viewTapped() {
view.endEditing(true)
}
Then we add the following check into UIGestureRecognizerDelegate:
extension StadtViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: self.tableView) == true {
return false
} else {
view.endEditing(true)
return true
}
}
}
If I want to hide keyboard first, the tableView remains visible and responsive to my taps.
enter image description here
Simple solution is using UIControl instances in UITableViewCell to get touches. You can add any views with userInteractionEnables == NO to UIControl to get taps.
For CollectionView in Swift 5:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if gestureRecognizer is UITapGestureRecognizer {
let location = touch.location(in: mCollectionView)
return (mCollectionView.indexPathForItem(at: location) == nil)
}
return true
}
While it's late and many people find that the above suggestions work fine, I could not get Jason's or TMilligan's methods to work.
I have a Static tableView with multiple cells containing textFields that receive Number inputs using only the Number Keyboard. This was ideal for me:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if(![touch.view isKindOfClass:[UITableViewCell class]]){
[self.firstTF resignFirstResponder];
[self.secondTF resignFirstResponder];
[self.thirdTF resignFirstResponder];
[self.fourthTF resignFirstResponder];
NSLog(#"Touches Work ");
return NO;
}
return YES;
}
Ensure that you have implemented this <UIGestureRecognizerDelegate> in your .h file.
This line ![touch.view isKindOfClass:[UITableViewCell class]] checks whether a tableViewCell was tapped and dismisses any active keyboard.
Solution for Swift, works in 2021. For this solution you don't have to have the reference(s) to the Table View(s).
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
//return !(touch.view is UITableViewCell) <-- doesn't work, the type of the touched class is not UITableViewCell anymore
var v = touch.view
while v != nil {
if v is UITableView { return false }
v = v?.superview
}
return true
}
Usage:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(YOUR_METHOD))
gestureRecognizer.delegate = self
YOUR_VIEW.addGestureRecognizer(gestureRecognizer)
Here is my solution, which ties the recognizer's shouldReceiveTouch directly to whether the keyboard is showing.
In your tap gesture recognizer delegate:
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([PFXKeyboardStateListener sharedInstance].visible) {
return YES;
}
return NO;
}
And the PFXKeyboardStateListener.h:
#interface PFXKeyboardStateListener : NSObject
{
BOOL _isVisible;
}
+ (PFXKeyboardStateListener *)sharedInstance;
#property (nonatomic, readonly, getter=isVisible) BOOL visible;
#end
And the PFXKeyboardStateListener.m:
static PFXKeyboardStateListener *sharedInstance;
#implementation PFXKeyboardStateListener
+ (PFXKeyboardStateListener *)sharedInstance
{
return sharedInstance;
}
+ (void)load
{
#autoreleasepool {
sharedInstance = [[self alloc] init];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)isVisible
{
return _isVisible;
}
- (void)didShow
{
_isVisible = YES;
}
- (void)didHide
{
_isVisible = NO;
}
- (id)init
{
if ((self = [super init])) {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(didShow) name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:#selector(didHide) name:UIKeyboardWillHideNotification object:nil];
}
return self;
}
#end
You may want to update the singleton pattern of the keyboard listener, I haven't gotten to it yet. Hope this works for everyone else as well as it works for me. ^^
Implement this method for delegate of UIGestureRecognizer:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
UIView *superview = touch.view;
do {
superview = superview.superview;
if ([superview isKindOfClass:[UITableViewCell class]])
return NO;
} while (superview && ![superview isKindOfClass:[UITableView class]]);
return superview != nil;
}
My case was different including a uisearchbar and uitableview on self.view. I wanted to dismiss uisearchbar keyboard by touching on the view.
var tapGestureRecognizer:UITapGestureRecognizer?
override func viewWillAppear(animated: Bool) {
tapGestureRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
}
On UISearchBar Delegate Methods:
func searchBarShouldBeginEditing(searchBar: UISearchBar) -> Bool {
view.addGestureRecognizer(tapGestureRecognizer!)
return true
}
func searchBarShouldEndEditing(searchBar: UISearchBar) -> Bool {
view.removeGestureRecognizer(tapGestureRecognizer!)
return true
}
When user touches on self.view:
func handleTap(recognizer: UITapGestureRecognizer) {
sampleSearchBar.endEditing(true)
sampleSearchBar.resignFirstResponder()
}
In swift you can use this inside
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if CheckTheTime() == true {
// do something
}else{
}
}
func CheckTheTime() -> Bool{
return true
}
This is my solution based on above answers...
It's worked for me...
//Create tap gesture for menu transparent view
UITapGestureRecognizer *rightTableTransparentViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(rightTableTransparentViewTapMethod:)];
[rightTableTransparentViewTap setCancelsTouchesInView:NO];
[_rightTableTransparentView addGestureRecognizer:rightTableTransparentViewTap];
- (void)rightTableTransparentViewTapMethod:(UITapGestureRecognizer *)recognizer {
//Write your code here
}
ISSUE:
In my case, the issue was that I originally placed a button in each collectionView cell and set the constraints to fill the cell, so that when the cell was clicked it would click the button, however the buttons function was empty so nothing was appearing to be happening.
FIX:
I fixed this by removing the button from the collection view cell.

Can you attach a UIGestureRecognizer to multiple views?

UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTapTap:)];
[self.view1 addGestureRecognizer:tapGesture];
[self.view2 addGestureRecognizer:tapGesture];
[tapGesture release];
In the above code only taps on view2 are recognized. If I comment out the third line then taps on view1 are recognized. If I'm right and you can only use a gesture recognizer once, I'm not sure if this is a bug or it just needs some more documentation.
A UIGestureRecognizer is to be used with a single view. I agree the documentation is spotty. That UIGestureRecognizer has a single view property gives it away:
view
The view the gesture recognizer is attached to. (read-only)
#property(nonatomic, readonly) UIView *view
Discussion You attach (or add) a gesture recognizer to a UIView object
using the addGestureRecognizer:
method.
I got around it by using the below.
for (UIButton *aButton in myButtons) {
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
longPress.minimumPressDuration=1.0;
[aButton addGestureRecognizer:longPress];
[longPress release];
}
Then in my handleLongPress method I just set a UIButton equal to the view of the gesture recognizer and branch what I do based upon that button
- (void)handleLongPress:(UILongPressGestureRecognizer*)gesture {
if ( gesture.state == UIGestureRecognizerStateEnded ) {
UIButton *whichButton=(UIButton *)[gesture view];
selectedButton=(UIButton *)[gesture view];
....
}
For Swift 3 in case anyone requires this:
Based on Bhavik Rathod Answer above.
func setGestureRecognizer() -> UIPanGestureRecognizer {
var panRecognizer = UIPanGestureRecognizer()
panRecognizer = UIPanGestureRecognizer (target: self, action: #selector(pan(panGesture:)))
panRecognizer.minimumNumberOfTouches = 1
panRecognizer.maximumNumberOfTouches = 1
return panRecognizer
}
///set the recognize in multiple views
view1.addGestureRecognizer(setGestureRecognizer())
view2.addGestureRecognizer(setGestureRecognizer())
No you should not attach gesture recognizers to more than one view.
There is this explicit information in the Apple documentation:
Gesture Recognizers Are Attached to a View
Every gesture recognizer is associated with one view. By contrast, a
view can have multiple gesture recognizers, because a single view
might respond to many different gestures. For a gesture recognizer to
recognize touches that occur in a particular view, you must attach the
gesture recognizer to that view.
Event Handling Guide for iOS - Gesture Recognizers Apple Developer Library
While as others mention they might work in some cases it is clearly against the documentation and could change in any future iOS version.
What you can do is add separate gesture recognisers to the views you want to monitor and they can share a common action.
We can do something Like this, it's easy and simple
1) create function as below in your controller (this function will return GestureRecognizer)
-(UITapGestureRecognizer*)setRecognizer{
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(openProfile)];
[gestureRecognizer setNumberOfTapsRequired:1];
return gestureRecognizer;
}
2) now set this recognizer in multiple views
[self.view1 addGestureRecognizer:[self setRecognizer]];
[self.view2 addGestureRecognizer:[self setRecognizer]];
Well if someone does not want to code for adding gesture view for multiple buttons like kwalker has answered above, and want to do it via Interface Builder this may help you.
1) You can add Long Press gesture Recognizer from Object Library like you add other objects like UIButtons and UILabels.
Initially what I ended up using was I took only one
2) Set referencing outlets to UIButton and sent actions with File's Owner.
Note: If you have multiple UIButton or any other object you will need separate gesture recognizer for each of them. For more details please refer to this question of mine.Getting wrong UIButton tag on Long press gesture recognizer
if you have fixed view I suggest you doing something like this
[self.view1 addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTapTap:)]];
[self.view2 addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapTapTap:)]];
that way will reduce multiple different useless variable
You could create a generic extension on view to add gesture recognizers easily.
This is just an example but it could look like this
extension UIView {
func setGestureRecognizer<Gesture: UIGestureRecognizer>(of type: Gesture.Type, target: Any, actionSelector: Selector, swipeDirection: UISwipeGestureRecognizer.Direction? = nil, numOfTaps: Int = 1) {
let getRecognizer = type.init(target: target, action: actionSelector)
switch getRecognizer {
case let swipeGesture as UISwipeGestureRecognizer:
guard let direction = swipeDirection else { return }
swipeGesture.direction = direction
self.addGestureRecognizer(swipeGesture)
case let tapGesture as UITapGestureRecognizer:
tapGesture.numberOfTapsRequired = numOfTaps
self.addGestureRecognizer(tapGesture)
default:
self.addGestureRecognizer(getRecognizer)
}
}
}
To add a 2 tap recognizer on a view you would just call:
let actionSelector = #selector(actionToExecute)
view.setGestureRecognizer(of: UITapGestureRecognizer.self, target: self, actionSelector: actionSelector, numOfTaps: 2)
You could also easily add a swipe recognizer
view.setGestureRecognizer(of: UISwipeGestureRecognizer.self, target: self, actionSelector: actionSelector, swipeDirection: .down)
and so on.
Just remember that the target must be linked to the selector.
Override class by '<UIScrollViewDelegate>'
And use this method in .m class:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
This method will help you to enable multiple swipe on a single view..
What about re write (recreate) your GestureRecognize every time that you add a gesture recognizer pointing to the same func.
In below case it works. I am using IBOutletCollection
Swift 2:
#IBOutlet var topicView: [UIView]!
override func viewDidLoad() {
for view in self.topicView as [UIView] {
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "viewClicked:"))
}
}
func viewClicked(recognizer: UITapGestureRecognizer) {
print("tap")
}
I know this is an old post but I figured something similar and hopefully it's useful someone else. I simply stored my imageViews in an array and assigned it to to the same gesture recognizer in a function to set up each image view.
In my viewDidLoad():
imageViewList = [imageView, imageView2, imageView3]
setupImageViews(imageViews: imageViewList)
Function to setup image views:
func setupImageViews(imageViews: [UIImageView]) {
for imageView in imageViews {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageTapped(tapGestureRecognizer:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizer)
//set up image to be displayed with the right aspect
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleBottomMargin, .flexibleRightMargin, .flexibleLeftMargin, .flexibleTopMargin]
imageView.contentMode = .scaleAspectFit // OR .scaleAspectFill
imageView.clipsToBounds = true
}
}
And in the action selector imageTapped(), you can have corresponding code for whichever image view tapped.
#objc func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
switch tapGestureRecognizer.view {
case imageView:
print("tapped Image View 1") //add your actions here
case imageView2:
print("tapped Image View 2") //add your actions here
case imageView3:
print("tapped Image View 3") //add your actions here
default:
print("Tap not detected")
}
_ = tapGestureRecognizer.view as! UIImageView
//additional code...
}
You can do it using this code my views which are imageviews in the xib.
- (void)viewDidLoad
{
firstIV.tag = 501;
secondIV.tag = 502;
thirdIV.tag = 503;
forthIV.tag = 504;
[self addTapGesturetoImageView: firstIV];
[self addTapGesturetoImageView: secondIV];
[self addTapGesturetoImageView: thirdIV];
[self addTapGesturetoImageView: forthIV];
}
-(void)addTapGesturetoImageView:(UIImageView*)iv
{
iv.userInteractionEnabled = YES;
UITapGestureRecognizer * textfielBGIVTapGasture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textfielBGIVTapped:)];
textfielBGIVTapGasture.numberOfTapsRequired = 1;
[iv addGestureRecognizer:textfielBGIVTapGasture];
}
- (void)textfielBGIVTapped:(UITapGestureRecognizer *)recognizer {
int tag = recognizer.view.tag-500;
switch (tag) {
case 1:
{
//firstIV tapped;
break;
}
case 2:
{
//secondIV tapped;
break;
}
case 3:
{
//thirdIV tapped;
break;
}
case 4:
{
//forthIV tapped;
break;
}
default: {
break;
}
}
}

Resources