I have a UITextField with the clear button enabled. I also have some code that dismisses the keyboard when the user taps anywhere else on the screen:
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
func dismissKeyboard() {
view.endEditing(true)
}
The problem is that this code interferes with the clear button, so the text field is never cleared. How do I prevent the tap gesture recognizer from handling the clear button tap?
Figured out a way to prevent this from happening.
So the clear button is of type UIButton, so we don't want the gesture recognizer to recognize those taps.
This function is called when the gesture recognizer receives a tap:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
// Don't handle button taps
return !(touch.view is UIButton)
}
The line:
return !(touch.view is UIButton)
Will return false if the place the user tapped was a UIButton and true otherwise.
So false = tap will not be handled by the gesture recognizer, and true = tap will be handled.
Don't forget to add the UIGestureRecognizerDelegate and set it using the following line:
tap.delegate = self
Related
I have a message chat box similar to most chat apps. The box goes up when when you start editing the messagetextView.
As is standard, there is a tap gesture recognizer that is called when the user taps anywhere else, which dismisses the keyboard.
I have another button that is visible next to the chat box.
Currently when the user taps the button, instead of triggering that ibaction, it dismisses the keyboard. Then when they tap on it again the ibaction is called. So the user has to tap twice to trigger the button when the keyboard is up.
Is there a way configure the button or gesture recognizer so that they both get called when the user taps in that location?
alternatively, is there a better design solution to solve something like this?
note: I read that in this situation ios will either choose the responder tree or gesture recognizer tree. So perhaps traversing both. How?
There are several ways to solve this problem.
It seems like the view your add tap gesture is above the button.
Set tapGesture's delegate and implement the UIGestureRecognizerDelegate.
Disable the tap gesture when tap the button.
class MessageViewController: UIViewController {
private lazy var button = UIButton(type: .custom)
weak var tapGesture: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard(_:)))
tap.delegate = self
view.addGestureRecognizer(tap)
}
#objc private func dismissKeyboard(_ sender: Any?) {
// dismiss the keyboard
}
#objc private func nextButtonOnClicked(_ sender: Any?) {
// dismiss the keyboard if need
// go next
}
}
extension MessageViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let tapGesture = tapGesture,
gestureRecognizer == tapGesture
else { return true }
if button.frame.contains(tapGesture.location(in: view)) {
return false
} else {
return true
}
}
}
Or just set the tapGesture's view below the button.
I have a UICollectionView, which I can search through using a UISearchBar. I set it up so that when the user taps anywhere on the screen, the keyboard is dismissed.
In viewDidLoad():
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
Then:
#objc override func dismissKeyboard() {
view.endEditing(true)
searchBar.endEditing(true)
}
It works at dismissing the keyboard but this tap gesture recognizer is getting in the way of selecting UICollectionView cells. The didSelectItemAt method just won't work.
Looking at another answer here, I managed to fix it somewhat by removing the gesture recognizer and just adding dismissKeyboard() in the didSelectItemAt. However, now it only dismisses if you tap the cell, and then the item selects (which I don't want, I just want the keyboard to dismiss).
How do I make it so that tapping anywhere on the screen when the keyboard is showing dismisses it, after which the UICollectionView cells work and can be selected?
Thanks!
you need to extend UIGestureRecognizerDelegate in your viewcontroller and add this snips of code. then tap gesture will not work for collectionview and act normally for rest of view.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view != self.yourCollectionView{
return false
}else{
return true
}
}
In didSelectItemAt You can check, is Your UISearchBar is first responder.
if searchBar.isFirstResponder {
searchBar.endEditing(true)
} else {
//do what You want
}
If You have another things, apart from cells, add Your gesture recognizer to dismiss keyboard
Ended up fixing it by adding a transparent view on top of everything and applying the gesture recognizer to it. On viewDidLoad() I set the view to isHidden = true.
Then added these:
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
gestureView.isHidden = false
return true
}
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
gestureView.isHidden = true
return true
}
Perhaps try tapGesture.cancelsTouchesInView = false
override func viewWillAppear(_ animated: Bool) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideKeyboard))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
}
#objc func hideKeyboard() {
searchController.searchBar.resignFirstResponder()
view.endEditing(true)
}
I have ViewController with UIGestureRecognizer implemented this way:
// Extension for GestureRecognizer
extension UIViewController {
func addGestureRecognizer() {
let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(singleTap)
}
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
self.view.endEditing(false)
}
}
As you can see, cancelsTouchesInView is set to false by default. It works fine with main view with searchBar and textFields. Keyboard is being dismissed and touches are translated to view's objects - buttons and so on.
But I have added subview that works like AlertView with several UITextFields and UIButtons. And here is a problem. When subview is presented and I'm tapping on any button of this subview - gestureRecognizer reacts and keyboard disappears. But nothing else happens. So I have to tap the second time on the button to make it pressed.
I have tried to change target of UITapGestureRecognizer from self to self.mySubViewName, like this:
func addGestureRecognizer() {
let singleTap = UITapGestureRecognizer(target: self.addingItemView, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(singleTap)
}
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
self.view.endEditing(false)
}
This way GestureRecognizer started to work with subview buttons properly, but is not able to work with main view. I started getting "Unrecognized selector was sent" error when trying to dismiss keyboard for searchbar for example. I have tried to implement separate function addGestureRecognizerForSubView, but still getting "Unrecognized selector was sent..." error.
Also tried to add this:
extension MenuTableViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == self.addingItemView
}
}
but nothing changes.
I've checked a lot of answers here, but was not able to find my case. Can anybody advise? I can share my ViewController class, but it's too heavy and looks ugly with programmatically added subview :)
Add a subview behind the alert and add the gesture to it not to self.view
let singleTap = UITapGestureRecognizer(target: self.otherView, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.otherView.addGestureRecognizer(singleTap)
Edit: your suggest to add it to a subview is right
let singleTap = UITapGestureRecognizer(target: self.addingItemView, action: #selector(self.handleTap(_:)))
singleTap.cancelsTouchesInView = false
singleTap.numberOfTapsRequired = 1
self.view.addGestureRecognizer(singleTap)
But you still add the gesture to self.view here
self.view.addGestureRecognizer(singleTap)
change to
self.addingItemView.addGestureRecognizer(singleTap)
I want to add a gesture recognizer to one of my views to detect taps,
here is my code:
class DateTimeContainer: UIView, UIGestureRecognizerDelegate {
override func awakeFromNib() {
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onTap))
gesture.delegate = self
self.addGestureRecognizer(gesture)
}
func onTap(_ gestureRecognizer: UITapGestureRecognizer) {
openDatePicker()
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.tag != self.datePickerTag && !self.isDatePickerOpen() {
return true
}
return false
}
}
When I tap on my view the code enters into the gestureRecognizer shouldReceive method and enters the condition for which it returns true.
But the onTap method is never called, can someone tell me why?
EDIT
Adding self.isUserInteractionEnabled = true I managed to get it working.
But it had a strange behaviour: it was like it received the tap only in the main view and not in subviews.
So to make it simple I solved by adding a button inside my view and by using:
self.button?.addTarget(self, action: #selector(onTap), for: .touchUpInside)
You may have forgotten to do this:
self.isUserInteractionEnabled = true
Also, typically the UIViewController subclass is usually the target and contains the method used for the tap gesture.
You don't need to set a delegate for a tap gesture (usually)
You can You Gesture
let recognizer = UITapGestureRecognizer(target: self,action:#selector(self.handleTap(recognizer:)))
userImage.isUserInteractionEnabled = true
recognizer.delegate = self as? UIGestureRecognizerDelegate
userImage.addGestureRecognizer(recognizer)
And method call selector
func handleTap(recognizer:UITapGestureRecognizer) {
// do your coding here on method
}
Try Writing the codes
let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onTap))
gesture.delegate = self
self.addGestureRecognizer(gesture)
inside init block not in awakeFromNib()
I noticed that you are adding your gesture on a UIView. As such, at that ViewController where you have added this ContainerView, inside the viewDidLoad method, add this line of code.
override func viewDidLoad() {
super.viewDidLoad()
self.YourContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(YourCurrentViewController.openDatePicker())))
self.YourContainerView.isUserInteractionEnabled = true
}
Note. Do not forget to add isUserInteractionEnabled to true.
I am using this code to dismiss keyboard when user click's outside the TextField
override func viewDidLoad() {
...
let tapGesture = UITapGestureRecognizer(target: self, action: "tap:")
view.addGestureRecognizer(tapGesture)
...
}
func tap(gesture: UITapGestureRecognizer) {
txtName.resignFirstResponder()
}
It is working when the user click's anywhere outside the textfield but the datepicker. When he put's a name and then click on the DatePicker (just click, not roll) the tap is not recognized.
What should I do to make it work?
It's possible the gesture recogniser on the DatePicker is interfering with yours. See if modifying this function helps your case.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true //Obviously think about the logic of what to return in various cases
}