iOS addGestureRecognizer with cancelsTouchesInView = false does not work with subview buttons - ios

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)

Related

Combining LongPress with Swipe UIGesture in Swift 5 (Xcode 10.1)

I've seen other people ask similar questions but I didn't really understand the answers. I'm new to iOS design and relatively new to Swift, so I apologize for my ignorance. My goal is to have the user swipe in a given direction and have the program repeat a certain action until the user lifts his finger from the screen. From what I can tell, I need to implement the gestureRecognizer function.
I think I probably implemented things in a weird way:
In my GameViewController.swift file, I implemented the following, as a separate function under the UIViewController class.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)-> Bool {
return true
}
Which I understand to mean that any two UIGestureRecognizer actions can occur at the same time.
In my GameScene.swift file, under the SKScene class, I have:
override func didMove(to view: SKView) {
...
swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(swipeR))
swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(swipeL))
longPress = UILongPressGestureRecognizer(target: self, action: #selector(printLongPress))
longPress.delegate = self as? UIGestureRecognizerDelegate
swipeRight.delegate = self as? UIGestureRecognizerDelegate
swipeLeft.delegate = self as? UIGestureRecognizerDelegate
swipeRight.direction = .right
swipeLeft.direction = .left
view.addGestureRecognizer(swipeRight)
view.addGestureRecognizer(swipeLeft)
view.addGestureRecognizer(longPress)
}
#objc func swipeR() {
print("Right")
}
#objc func swipeL() {
print("Left")
}
#objc func printLongPress() {
print("Long press")
}
Ideally in the example code described here, if you were to swipe right and hold onto the screen, you'd see "Swipe Right" "Long Press" "Long Press" etc.
Any ideas what I'm doing wrong here?
Thanks in advance!

Triggering UITapGestureRecognizers from overlapping Views

I have one main view and 4 subviews of the mainview they all have their UITapGestureRecognizer, when I tap on one subview how can it be triggered both views. Example below,
if I tap to subview 1 desired log would be:
subview1 is clicked
MainView is clicked
My Code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let mainGesture = UITapGestureRecognizer(target: self, action: #selector(mainGestureActivated))
self.view.addGestureRecognizer(mainGesture)
let subGesture = UITapGestureRecognizer(target: self, action: #selector(subViewGestureActivated))
self.subview1.addGestureRecognizer(subGesture)
}
#objc func mainGestureActivated(){
print("MainView Clicked")
}
#objc func subViewGestureActivated(){
print("Subview Clicked")
}
it prints only subview clicked! Is it possible to trigger both gestureRecognizers since main is encapsulating other.
First you should conform to UIGestureRecognizerDelegate in your VC, and then implement the delegate func of shouldRecognizeSimultaneouslyWith. Inside the function, you should detect if the gestureRecognizer, and the otherGestureRecognizer are the wants you want, and if so, you should allow them to work simultaneously,
Conform to delegate, and Declare gesture recognizers outside of viewDidLoad (because you need to access them in the delegate method later.)
var mainGestureRecognizer = UITapGestureRecognizer()
var subGestureRecognizer = UITapGestureRecognizer()
Initialize your recognizers, and set your VC as their delegate:
mainGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(mainGestureActivated))
subGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(subViewGestureActivated))
mainGestureRecognizer.delegate = self
subGestureRecognizer.delegate = self
Implement the delegate function mentioned above to allow simultaneous recognition for subView and mainView:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == subGestureRecognizer && otherGestureRecognizer == mainGestureRecognizer {
return true
}
return false
}
And if you want it to work for 4 different subviews, then you should check with else if statements inside the delegate method.

Fast tap gestures in Swift 4 and iOS 11

I just made a simple app to try out any type of gestures. I got to the tap gesture. So I thought, what if I made a fast tap game kind of application that counts the amount of taps the user performed. But soon enough I ran into some issues.
It did not count all the taps. If I began to tap as fast as possible, but it skipped taps.
The idea is I programmatically created a view in the superview and added a tapGestureRecognizer on the view. And simply put the “taps” into a label in the app.
It seems to fail to receive a system gesture on time.
The code:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped(sender:)));
tap.numberOfTapsRequired = 1;
animationView.isUserInteractionEnabled = true;
animationView.addGestureRecognizer(tap);
The function:
#objc func tapped (sender :UITapGestureRecognizer) {
self.counter += 1;
self.lblScore.text = String(self.counter);
}
I have an animationView that I made "tappable" and it works. Every time I tap the animationView it increments the value of 'counter' that works! but every time I get this error if I tap too fast:
<_UISystemGestureGateGestureRecognizer: 0x1c01c4b00>: Gesture: Failed to receive system gesture state notification before next touch
Create Gesture :
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
panGesture.delegate = self
Button.addGestureRecognizer(panGesture)
let longTapGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongTapGesture(_:)))
Button.addGestureRecognizer(longTapGesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_:)))
Button.addGestureRecognizer(tapGesture)
let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotateGesture(_:)))
rotateGesture.delegate = self
Button.addGestureRecognizer(rotateGesture)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture(_:)))
pinchGesture.delegate = self
Button.addGestureRecognizer(pinchGesture)
Gesture click Event :
extension AddTextVC: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
#IBAction func handlePanGesture(_ recognizer: UIPanGestureRecognizer) {
}
#IBAction func handlePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
}
#IBAction func handleRotateGesture(_ recognizer: UIRotationGestureRecognizer)
{
}
#IBAction func handleTapGesture(_ recognizer: UITapGestureRecognizer) {
}
#IBAction func handleLongTapGesture(_ recognizer: UITapGestureRecognizer) {
}
}

Tap Gesture Recognizer Interfering with Clear Button

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

Swift 3 UITapGestureRecognizer not receiving tap

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.

Resources