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)
}
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 this Setup in my Storyboard.
In my first ViewController Scene I have a MapView from MapBox. In there I have put a TextField (AddressTextField). On that TextField when touching the view, i'm running self.addressTextField.resignFirstResponder(), but after that neither the mapview, nor any other element in there or in the Embedded Segues react on a touch or click. Probably this is because I didn't completely understand the system of the First Responder. I'm thankful for every help.
Edit 1:
I think I know what's going on now, but I don't know how to fix it. When I add the Gesture Recognizer to the View (or to the mapView, that doesn't matter), the other UIViews and the MapView do not recognize my Tap-Gestures anymore. When I am not adding the Recognizer everything works fine. It seems as if the Gesture Recognizer is recognizing every tap I make on either the UIViews or the MapView and therefore other gestures are not recognized.
Edit 2:
I just added a print() to dismissKeyboard(). As soon as any Touch Event gets recognized on the MapView or the other UIViews, dismissKeyboard() gets called. So I think my thought of Edit 1 was correct. Does anyone know how I can solve this, so that it's not only dismissKeyboard() that gets called ?
Some Code:
func dismissKeyboard(){
self.addressTextField.resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
dismissKeyboard()
return true
}
//Class (only partially)
class ViewController: UIViewController, MGLMapViewDelegate, CLLocationManagerDelegate, UITextFieldDelegate {
override func viewDidLoad(){
mapView.delegate = self
addressTextField.delegate = self
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.mapView.addGestureRecognizer(tap)
}
}
Others are just #IBActions linked to the Buttons, or other elements.
try this:
func dismissKeyboard(){
view.endEditing(true)
}
hope it helps!
After I knew the real issue I was able to solve the problem. I declared a var keyboardEnabled. Then I added these lines to my class.
class ViewController: UIViewController, UIGestureRecognizerDelegate {
var keyboardEnabled = false
override func viewDidLoad(){
super.viewDidLoad()
//Looks for single tap
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.mapView.addGestureRecognizer(tap)
}
/* Setting keyboardEnabled */
//Editing Target did end
#IBAction func editingTargetDidEnd(_ sender: Any) {
keyboardEnabled = false
}
//Editing TextField Started
#IBAction func editingAdressBegin(_ sender: Any) {
keyboardEnabled = true
}
//Call this function when the tap is recognized.
func dismissKeyboard() {
self.mapView.endEditing(true)
keyboardEnabled = false
}
//Implementing the delegate method, so that I can add a statement
//decide when the gesture should be recognized or not
//Delegate Method of UITapGestureRecognizer
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return keyboardEnabled
}
}
With this solution keyboardEnabled takes care of deciding wether my UIGestureRecognizer should react or not. If the Recognizer doesn't react, the Gesture is simply passed on to the UIViews or other Elements that are in my MapView.
Thanks for all your answers!
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'm using a text view inside a table cell. Other cells are associated with segues so I need the following behaviour:
(Default) Tapping on other table rows triggers relevant segue
Tapping inside the text view begins editing and shows the keyboard
If the keyboard is visible, tapping outside the text view dismisses the keyboard but does not follow segues
Once the keyboard has been dismissed, subsequent taps on segues are followed.
1. Adopt the textView delegate method and
textView.delegate = self
2. Add global property
var tap = UITapGestureRecognizer()
3. Implement the textView delegate methods
func textViewDidBeginEditing(textField: UITextView) {
self.tableView.allowsSelection = false
tap = UITapGestureRecognizer(target: self, action: #selector(self.Tapped))
self.view.addGestureRecognizer(tap)
}
func textViewDidEndEditing(textField: UITextView) {
self.tableView.allowsSelection = true
self.view.removeGestureRecognizer(tap)
}
4. Implement the Tapped()
func Tapped(){
view.endEditing(true)
}
Note : This also works for UITextField, replacing View with Field throughout
Add the UITextViewDelegate protocol to your class and add these variables
var textViewIsEditing:Bool = false
var tap: UITapGestureRecognizer!
Override the viewdidload function so that the tableViewController listens for events from the text view.
Also add a UITapGestureRecognizer which will trigger a custom handleTap function
override func viewDidLoad() {
super.viewDidLoad()
myTextView.delegate = self
tap = UITapGestureRecognizer(target: self, action: #selector(MyClass.handleTap))
tap.cancelsTouchesInView = false
self.view.addGestureRecognizer(tap)
}
Add two functions to toggle textViewIsEditing based on whether the textview is being edited
func textViewDidEndEditing(textView: UITextView) {
textViewIsEditing = false
}
func textViewDidBeginEditing(textView: UITextView) {
textViewIsEditing = true
}
Now add a custom handleTap function to decide how to handle taps:
func handleTap(thisGestureRecognizer:UITapGestureRecognizer){
if(textFieldIsEditing){
// ignore this tap if editing text field but end editing
tap.cancelsTouchesInView = true
view.endEditing(true)
}else{
// proceed as normal
tap.cancelsTouchesInView = false
}
}
I have a "search" UITextField at the top of my view.
Below this, I have a UICollectionView which is populated with the search results as the user types.
When a user is typing into the UITextView, the keyboard is displayed. At first, I wanted to hide the keyboard if the user touched anywhere outside the UITextField. I accomplished this with the following:
func textFieldDidBeginEditing(textField: UITextField) {
if (textField == self.textFieldSearch) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textFieldSearchDidChange:", name: UITextFieldTextDidChangeNotification, object: textField)
}
var tapGesture = UITapGestureRecognizer(target: self, action: "dismissKeyboard:")
self.view.addGestureRecognizer(tapGesture)
}
func dismissKeyboard(gesture: UIGestureRecognizer) {
self.textFieldSearch.resignFirstResponder()
self.view.removeGestureRecognizer(gesture)
}
However, if the user taps on a UICollectionViewCell, the dismissKeyboard func runs, and hides the keyboard, but the user has to tap on the cell again to run the func:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
How do I do this all in one step? So if the user touches anywhere outside the UITextField, hide the keyboard...but if the user happens to touch a UICollectionViewCell, run the didSelectItemAtIndexPath function on the first touch as well as hide the keyboard and end editing on the UITextField?
Any help is appreciated!
Thanks
Please try this one
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view == self.textFieldSearch{
return false;
}
else{
return true;
}
}
and add this line in your code
var tapGesture = UITapGestureRecognizer(target: self, action: "dismissKeyboard:")
tapGesture.delegate = self // add gesture delegate here
self.view.addGestureRecognizer(tapGesture)
You can implement the delegate method of UIGestureRecognizerDelegate
optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldReceiveTouch touch: UITouch) -> Bool
Above method returns NO for the views on which gesture recognizer should not be called and in your case it should be collection view else for other things return YES.
example implementation-
/**
Disallow recognition of tap gestures on the collection view.
*/
func gestureRecognizer(recognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool
{
if touch.view == collectionView && recognizer == tapRecognizer {
return false
}
return true
}
Here's a solution. A little janky but it should do the trick. You might have to tinker with it a little. Also my Swift isn't top notch so excuse any syntax errors.
func dismissKeyboard(gesture: UIGestureRecognizer) {
let point : CGPoint = gesture.locationInView(self.collectionView)
let indexPath : NSIndexPath = self.collectionView.indexPathForItemAtPoint(point)
self.textFieldSearch.resignFirstResponder()
self.view.removeGestureRecognizer(gesture)
self.collectionView.selectItemAtIndexPath(indexPath, animated:YES, scrollPosition:UICollectionViewScrollPosition.Top)
}
I am providing here a Objective-C code to dismiss keyBoard on any touch performed outside. Kindly write it in Swift for yourself. The problem is you are adding A tap Gesture which is hiding the touch events of your collection cell.
So instead of tap gesture use the Swift-translated code of the following code.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.view endEditing:YES];
}
Dont use your tap gesture. And I think things should get going for you...
EDIT
Some efforts to write the above code in swift for you...
override func touchesBegan(touches: NSSet, withEvent event: UIEvent){
self.view.endEditing(true)
}
This is the code that ended up finally working, with the help of #Sanjay above.
I still wanted to hide the keyboard if the user touched on the part of the collectionView that wasn't occupied by cells. I will still have to implement code for hiding the keyboard if a user 'swipes/scrolls/drags' on the UICollectionView instead of taps.
For now:
func gestureRecognizer(recognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
for cell:AnyObject in self.collectionView.visibleCells() {
if (touch.view.isDescendantOfView(cell as UIView)) {
self.resignAllResponders()
return false
}
}
return true
}