How to disable Long press gesture recogniser in UITextField and UISearchBar? - ios

I come across many post about disabling long press in UItextView, but that same process is not working for UITextField and UISearchBar.
Is there any way to disable Long press on UITextField and UISearchBar so I can avoid Magnifying glass on long press?
I have already checked "Disable Magnifying Glass in UITextField" but solution given there is not actually disabling magnifying glass. Its just not allowing cursory to Move between text. but still displaying Magnifying glass.
I want to disable Long Press - to avoid Problem which I am facing with magnifying glass. I want to disable it now. will enable it again when I will able to fix this.

You can try following for disabling only built in long press gesture
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
recognizer.enabled = NO;
}
}
or
delegate method for all :-
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return NO;
}

This combination in a custom class is the only thing I've been able to get to work for me, and on the first long press it still sometimes makes the magnifying glass appear:
//swift 4.2
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer)
{
if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
{
print(gestureRecognizer.name.unwrappedDebugString)
gestureRecognizer.isEnabled = false
}
return super.addGestureRecognizer(gestureRecognizer)
}
override func becomeFirstResponder() -> Bool
{
for recognizer in self.gestureRecognizers ?? []
{
if (recognizer is UILongPressGestureRecognizer)
{
recognizer.isEnabled = false
}
}
return super.becomeFirstResponder()
}

#Neeraj answer in Swift 4 :
class CustomTextView: UITextView {
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) {
gestureRecognizer.isEnabled = false
}
return super.addGestureRecognizer(gestureRecognizer)
}
}

One good and clean way to do this is you can create a custom class for the UITextField and in the custom class you can override the long-press gesture which is causing the magnification view appear.
This custom class can be used in all places where we don't want to show the magnification view for test field. Just use below class in place of UITextField class
class CustomTextView: UITextView {
override func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
if (gestureRecognizer.isKindOfClass(UILongPressGestureRecognizer)) {
gestureRecognizer.enabled = false;
}
super.addGestureRecognizer(gestureRecognizer)
return
}
}

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.

Disable Magnifing glass in UITextview, but not URL Link and Phone number events

I have an issue with UITextview in iOS9, when i do a longpress on textview it shows the magnifying glass. I tried to disable through UILongPressGestureRecognizer, it completely disables the Link and phone touch events also.
How to Disable only Magnifing glass.
override func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer .isKindOfClass(UILongPressGestureRecognizer){
gestureRecognizer.enabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
the textview will be in a collectionview cell.
There is a way to achieve that. Just override the gestureRecognizerShouldBegin for the UITextView. This gesture-recognizer object is about to begin processing touches to determine if its gesture is occurring.
The only tricky part is to properly recognize delegate which is assigned to the magnifier activation.
I've successfully tested with: UITextGestureClusterLoupe. Here is the working example (Swift 4):
override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool
{
if let gestureDelegate = gestureRecognizer.delegate {
if(gestureDelegate.description.localizedCaseInsensitiveContains("UITextGestureClusterLoupe"))
{
return false;
}
}
return true;
}
The code seems right but you need to override the gestureRecognizer for the textView, not the superclass. Change super.addGestureRecognizer(gestureRecognizer) to yourTextView.addGestureRecognizer(gestureRecognizer) and place it in the view controller containing an outlet to your text view(if it's not there already).
According to Vlada's anwser, for my code I found the delegate assigned to the magnifier activation is "UITextLoupeInteraction"
Here is my code:
open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if let gestureDelegate = gestureRecognizer.delegate {
print(gestureDelegate.description)
if(gestureDelegate.description.localizedCaseInsensitiveContains("_UIKeyboardBasedTextSelectionInteraction")){
return false
}
if(gestureDelegate.description.localizedCaseInsensitiveContains("UITextLoupeInteraction")){
return false
}
}
return true
}
My development environment: swift4, Xcode10.1, iOS 12.1

disabling swiping on uipageviewcontroller for certain area of screen

I have a uipageviewcontroller and the pages have an area on the screen where there is a uitableview. I want the user to only be able to swipe through pages outside of that uitableview.
I can't seem to find where these gesture recognizers are hiding. I am setting them up as delegates like this:
self.view.gestureRecognizers = self.pageViewController?.gestureRecognizers
for gesture in self.view.gestureRecognizers!{
// get the good one, i discover there are 2
if(gesture is UIPanGestureRecognizer)
{
println("ispan")
// replace delegate by yours (Do not forget to implement the gesture protocol)
(gesture as! UIPanGestureRecognizer).delegate = self
}
}
I am seeing ispan in the logs so it seems to find some uipangesturerecognizer but when I override the function like this:
func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
println("gesture should begin")
var point = gestureRecognizer.locationInView(self.view)
return true
}
it doesn't print out "gesture should begin" at all... I have the class set as a UIGestureRecognizerDelegate what am I doing wrong? I'm guessing I have the wrong gesture recognizers set as delegates how can I set the correct ones as delegates?
Could something like this work?
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if(touch.view == <your tableView>){
return false
}else{
return true
}
}
You might need to also test which gestureRecognizer it is (the one from the pageView or the one from the tableView).

How to disable UITextField editing but still accept touch?

I'm making a UITextField that has a UIPickerView as inputView. Its all good, except that I can edit by copy, paste, cut and select text, and I don't want it. Only the Picker should modify text field.
I've learned that I can disable editing by setting setEnabled or setUserInteractionEnabled to NO. Ok, but the TextField stop responding to touching and the picker don't show up.
What can I do to achieve it?
Using the textfield delegate, there's a method
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Return NO from this, and any attempt by the user to edit the text will be rejected.
That way you can leave the field enabled but still prevent people pasting text into it.
Translate the answer of Nick to swift:
P/S: Return false => the textfields cannot input, edit by the keyboard. It just can set text by code.EX: textField.text = "My String Here"
override func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return false
}
This would be the simplest of all:
in viewDidLoad:(set the delegate only for textfields which should not be editable.
self.textfield.delegate=self;
and insert this delegate function:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
return NO;
}
Thats it!
In swift 3+ :
class MyViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
self.myTextField.delegate = self
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
if textField == myTextField {
// code which you want to execute when the user touch myTextField
}
return false
}
}
Simply place a UIButton exactly over the entire UITextField with no Label-text which makes it "invisible". This button can receive and delegate touches instead of the Textfield and the content of the TextField is still visible.
It would be more elegant to create a custom subclass of UITextField that returns NO for all calls to canPerformAction:withSender: (or at least where action is #selector(cut) or #selector(paste)), as described here.
In addition, I'd also implement
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
as per Nick's suggestion in order to disable inputting text from Bluetooth keyboards.
In Swift:
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
questionField.resignFirstResponder();
// Additional code here
return false
}
I used the solution provided by MrMage. The only thing I'd add is you should resign the UITextView as first responder, otherwise you're stuck with the text selected.
Here's my swift code:
class TouchableTextView : UITextView {
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
self.resignFirstResponder()
return false
}
override func shouldChangeTextInRange(range: UITextRange, replacementText text: String) -> Bool {
self.resignFirstResponder()
return false
}
}
To prevent editing of UITextField while using UIPickerView for selecting values(in Swift):
self.txtTransDate = self.makeTextField(self.transDate, placeHolder: "Specify Date")
self.txtTransDate?.addTarget(self, action: "txtTransDateEditing:", forControlEvents: UIControlEvents.EditingDidBegin)
func makeTextField(text: String?, placeHolder: String) -> UITextField {
var textField = UITextField(frame: CGRect(x: 140, y: 0, width: 220.00, height: 40.00));
textField.placeholder = placeHolder
textField.text = text
textField.borderStyle = UITextBorderStyle.Line
textField.secureTextEntry = false;
textField.delegate = self
return textField
}
func txtTransDateEditing(sender: UITextField) {
var datePickerView:UIDatePicker = UIDatePicker()
datePickerView.datePickerMode = UIDatePickerMode.Date
sender.inputView = datePickerView
datePickerView.addTarget(self, action: Selector("datePickerValueChanged:"), forControlEvents: UIControlEvents.ValueChanged)
}
func datePickerValueChanged(sender: UIDatePicker) {
var dateformatter = NSDateFormatter()
dateformatter.dateStyle = NSDateFormatterStyle.MediumStyle
self.txtTransDate!.text = dateformatter.stringFromDate(sender.date)
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
self.resignFirstResponder()
return false
}
For an alternative that handles the UIPickerView and Action Sheets, checkout ActionSheetPicker
https://github.com/TimCinel/ActionSheetPicker
It's cocoapods enabled. It handles all of the cancel and done buttons on the Action Sheet. The examples within the sample project are great. I choose the ActionSheetStringPicker, which handles easily just String based options, but the API can handle most anything that I can think of.
I originally started a solution much like the checkmarked answer, but stumbled onto this project and took me roughly 20 minutes to get things integrated into my app for usage including using cocopods: ActionSheetPicker (~> 0.0)
Hope this helps.
Download the git project and look at the following classes:
ActionSheetPickerViewController.m
ActionSheetPickerCustomPickerDelegate.h
Here is roughly most of the code that I added, plus the *.h imports.
- (IBAction)gymTouched:(id)sender {
NSLog(#"gym touched");
[ActionSheetStringPicker showPickerWithTitle:#"Select a Gym" rows:self.locations initialSelection:self.selectedIndex target:self successAction:#selector(gymWasSelected:element:) cancelAction:#selector(actionPickerCancelled:) origin:sender];
}
- (void)actionPickerCancelled:(id)sender {
NSLog(#"Delegate has been informed that ActionSheetPicker was cancelled");
}
- (void)gymWasSelected:(NSNumber *)selectedIndex element:(id)element {
self.selectedIndex = [selectedIndex intValue];
//may have originated from textField or barButtonItem, use an IBOutlet instead of element
self.txtGym.text = [self.locations objectAtIndex:self.selectedIndex];
}
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return NO; // Hide both keyboard and blinking cursor.
}
if you are ready to create your custom textfield then you can just use this answer from another stackoverflow question.
https://stackoverflow.com/a/42698689/9369035
just override those three method as in above answer and that is enough. at least so far as I tested.
This workaround works. Put a transparent UIView above the text field and implement the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *press = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress)];
[transparentView addGestureRecognizer:press];
[press release];
press = nil;
}
-(void)longPress
{
txtField.userInteractionEnabled = NO;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
txtField.userInteractionEnabled = YES;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[txtField becomeFirstResponder];
}
Make your inputView be presented by an hidden textfield which also change the text of the presented and disabled one.
Not completely sure about how hacky this is but the only thing that did the trick in my case was adding a target action and calling endEditing. Since my picker controls my UITextField.text value, I could dismiss the keyboard as soon as the user clicks on the field. Here's some code:
uiTextFieldVariable.addTarget(self, action: #selector(dismissKeyboard), for: .editingDidBegin)
#objc private func dismissKeyboard() {
endEditing(true)
}
If you are using IQKeyboardManagerSwift pod then use textField.enableMode = .disabled
else If you are using RxSwift & RxCocoa pods then
textField.rx.controlEvent(.editingDidBegin)
.subscribe(onNext: {
[weak self] _ in let _ = self?.textField.endEditing(true)
}).disposed(by: bag)
else use delegate method of textFiled
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return false
}
I used :
[self.textField setEnabled:NO];
and its work fine
This worked for me
[textview setEditable:NO];
The above answers are overcomplicating the situation.

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.

Resources