The goal is to implement two pan gestures against the same SCNScene: one with one finger and the other with two fingers.
This code below isn't working. The one-finger pan function never gets invoked even though the gesture is assigned a distinct selector. One-finger pans and two-finger pans both invoke sceneViewPannedTwoFingers.
From reading other questions it seemed like shouldRecognizeSimultaneouslyWithGestureRecognizer might be the answer, but these pans are not happening simultaneously. It should be either a one-finger pan or a two-finger pan, never both at once.
Is it possible to have two pan gestures as described above? If so, what's the right way to do this?
// Handle one-finger pans
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(sceneViewPannedOneFinger))
sceneView.addGestureRecognizer(panRecognizer)
// Handle two-finger pans
let twoFingerPanRecognizer = UIPanGestureRecognizer(target: self, action: #selector(sceneViewPannedTwoFingers))
sceneView.addGestureRecognizer(twoFingerPanRecognizer)
func sceneViewPannedTwoFingers(sender: UIPanGestureRecognizer) {
print("two finger pan!!!")
}
func sceneViewPannedOneFinger(sender: UIPanGestureRecognizer) {
print("one finger pan!!!")
}
You need to add UIGestureRecognizerDelegate to your view controller and set the gesture recognizers delegate to self inside your view controller. Add the shouldRecognizeSimultaneouslyWithGestureRecognizer method returning true for them. Make sure you set minimum and maximum number of touches also for them.
code like this
class MyClass : UIGestureRecognizerDelegate --< Delegate
func gestureRecognizer(_: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool
{
return true
}
Related
I am trying to understand a reproducible bug with my gesture recognisers. I have 2 recognisers on an MKMapView, one UITapGestureRecognizer and one UILongPressGestureRecogniser. Both of them work as expected the first time, however, if I use the long press (which adds an annotation to the map) the next tap gesture will return in the 'possible' state but never hit the 'recognized' state.
▿ Optional<Array<UIGestureRecognizer>>
▿ some : 2 elements
- 0 : <UITapGestureRecognizer: 0x7fda7543ebc0; state = Possible; view = <MKMapView 0x7fda78026e00>>
- 1 : <UILongPressGestureRecognizer: 0x7fda7543e8c0; state = Possible; view = <MKMapView 0x7fda78026e00>; numberOfTapsRequired = 0; minimumPressDuration = 0.2>
After I tap once, and nothing happens, a second tap will then perform the associated function i.e. make it to the recognized state.
I am intercepting all the clicks on the window and the tap definitely takes place each time but the first one after a long press never seems to become accepted. Is there something I'm missing here? The gestures are added as below:
let mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
mapView.addGestureRecognizer(mapTap)
let pressGesture = UILongPressGestureRecognizer(target: self, action: #selector(mapLongPress(_:)))
pressGesture.minimumPressDuration = 0.2
pressGesture.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(pressGesture)
Could this be to do with the other gestures which are added by default on an MKMapView?
I tried using your code and got the same result.
I solved it with a tricky solution. I hope it would be helpful for you
mapTap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))
mapTap.delegate = self
mapView.addGestureRecognizer(mapTap)
pressGesture = UILongPressGestureRecognizer(target: self, action:
#selector(mapLongPress(_:)))
pressGesture.minimumPressDuration = 0.2
pressGesture.numberOfTouchesRequired = 1
mapView.addGestureRecognizer(pressGesture)
#objc func mapTapped(_ gesture: UITapGestureRecognizer) {
// your code
}
#objc func mapLongPress(_ gesture: UILongPressGestureRecognizer) {
// your code
if gesture.state == .began {
mapTap.isEnabled = false
} else if gesture.state == .cancelled || gesture.state == .ended {
mapTap.isEnabled = true
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
In your case you expect that the tap recognizer as well as the long press recognizer operate simultaneously: When you tap the view, both should start the recognition process. When you end the tap before the minimum tap time for the long press, the tap gesture should fire, but when you end the tap later, the long press gesture should fire.
But the Apple docs say:
UIKit normally allows the recognition of only one gesture at a time on
a single view. Recognizing only one gesture at a time is usually
preferable because it prevents user input from triggering more than
one action at a time. However, this default behavior can introduce
unintended side effects. For example, in a view that contains both pan
and swipe gesture recognizers, swipes are never recognized. Because
the pan gesture recognizer is continuous, it always recognizes its
gesture before the swipe gesture recognizer, which is discrete.
In your case, the long tap gesture recognizer is continuous while the tap gesture recognizer is discrete, so there could be a problem in recognizing the tap.
I would thus try to explicitly allow both recognizers to simultaneous recognice their gestures. An example how to do this is given here.
As soon as the long press recognizer fires, you could cancel the recognition operation of the tap recognizer.
Hope this helps!
I have the following case. parentView has it's own gestureRecognizerAand has a subview subView which has it's own UITapGestureRecognizer.
Is there any way to tell parentView that it should pass the touch events recognized in gestureRecognizerA to subView if these touch events are in subView's bounds?
gestureRecognizerA is very specific. It is a custom gesture recognizer for recognizing a circlular motion. This recognition should happen on all areas of parentView. However, when that same gesture recognizer recognizes a tap, it should pass that tap to subView.
You can easily identify the points of tap.
As for example you have a tap gesture in parent class as:
let tapGR = UITapGestureRecognizer(target: self, action: #selector(tapped))
view.addGestureRecognizer(tapGR)
#objc func tapped(gr:UITapGestureRecognizer) {
let loc:CGPoint = gr.location(in: gr.view)
//insert your touch based code here
}
Inside the tapped method you can identify the location where tap happened, so after checking bounds of the subview with location of tap you can verify is the tap happened inside the bounds of subview or not.
It seems like you just want both of those gesture recognizers to work simultaneously. Just implement UIGestureRecognizerDelegate for your parentView and make it tapGestureRecognizer's and gestureRecognizerA's delegate. Then implement an optional method there:
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) {
return true
}
That might be able to detect a tap in subView even while doing a circular motion within parentView.
UPDATE: When using gesture recognizers, "forwarding touches" would be to simply calling a method of another recognizer. Just put a recognizer which is doing the forwarding as its parameter.
For instance, tapGestureRecognizer fires viewWasTapped(_ sender: UITapGestureRecognizer) when a tap is detected. Now, when your gestureRecognizerA wants to forward its events to tapGestureRecognizer, it simply does so by calling:
subView.viewWasTapped(self.gestureRecognizerA)
With an obvious change to the method itself:
func viewWasTapped(_ sender: UIGestureRecognizer) {
// ...
}
This works for UITapGestureRecognizer. The sender can be any other UIGestureRecognizer and you'd still have almost all the information to resolve a tap gesture there.
I have a UITextView to which I have attached a gesture recognizer as follows:
let testTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTextViewTapped(gestureRecognizer:)))
testTapGestureRecognizer.cancelsTouchesInView = false
testTapGestureRecognizer.delaysTouchesBegan = false
testTapGestureRecognizer.delaysTouchesEnded = false
if textTextView != nil {
textTextView!.addGestureRecognizer(testTapGestureRecognizer)
}
The selector mentioned above is as follows:
#objc func textTextViewTapped(gestureRecognizer: UIGestureRecognizer) {
print("testTextViewTapped called.")
}
Every time I tap the UITextView, I can see the message above printed on the console. However, the keyboard doesn't appear any more.
I found Apple's doc confusing here:
Here, it says that
A gesture recognizer doesn’t participate in the view’s responder
chain.
which I am interpreting as that any gestures are also sent to the view and up the chain, as is normal.
Later on the same page, it says,
If a gesture recognizer recognizes its gesture, the remaining touches
for the view are cancelled.
which means that if an attached gesture recognizer is able to understand a gesture as the one it is supposed to recognize, then it will prevent it from being delivered to the view to which it is attached. Further, it specifies 3 properties that should be able to stop the gesture recognizer from doing that. I have set all three of them to false in my code, as shown above.
What is actually happening here and how do I allow the UITextView to interpret the touches normally and also be able to use a gesture recognizer?
You could use the UIGestureRecognizerDelegate to make the UITapGestureRecognizer work along the regular UITextView behaviour:
class TestViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap))
tapGestureRecognizer.delegate = self
textView.addGestureRecognizer(tapGestureRecognizer)
}
#objc private func tap() {
print("tap")
}
}
extension TestViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
The UITextView probably has its own private gesture recognizer to handle when a user taps on it. When this happens it makes the text view the first responder, which causes the keyboard to appear. Gesture recognizers can force other gesture recognizers to fail when they recognize their gesture. (See the docs) Perhaps this is what is happening when you add your tap gesture. It recognizes the tap and thus forces other gestures to fail, which prevents the text view from becoming the first responder.
The best solution is to follow the answer from this question (as was mentioned by #FrancescoDeliro in the comments) and use the delegate calls to know when editing is beginning/ending.
I am a beginner in Swift, and am trying to add a swipe gesture recognizer to my UIView. I have inserted a gradient CALayer to index 0 to have a gradient background.
My problem is:
Swipe gestures for right and left work fine, but for Down it doesn't work, why?
Set the delegate of swipe gestures that you are adding to the view.
let swipeGesture = UISwipeGestureRecognizer(target: self, action: Selector("handleSwipe:"))
swipeGesture.delegate = self
self.view.addGestureRecognizer(swipeGesture)
self.mySwipeGesture = swipeGesture
GestureRecognizerDelegate asks if two gesture recognizers should be allowed to recognize gestures simultaneously. Return true to allow both gestureRecognizer and otherGestureRecognizer to recognize their gestures simultaneously. The default implementation returns false—no two gestures can be recognized simultaneously. Implement the following delegate to achieve this.
extension ViewController : UIGestureRecognizerDelegate {
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
//Identify gesture recognizer and return true else false.
return gestureRecognizer.isEqual(self.mySwipeGesture) ? true : false
}
}
Swiping Up & Down are the default property of table view. I would suggest you to disable the scrolling of the table view whenever you want to do something on the overlay.
tableView.scrollEnabled = NO;
If you are performing dragging of a particular cell then long press on it and then start dragging.
This is how you can achieve this.
Hope this helps.
I'm working on a game using SpriteKit and I want to move some sprites around but I need to move only one sprite at a time. How can I use pan gesture recognizer in SpriteKit? I tried with the normal way and I got some errors so I thought maybe it has a special way.
To add a pan gesture recognizer in your game, add the gesture recognizer in your GameScene's didMove method. Then add a new function (handlePanFrom, below) in your GameScene file which will be called when the gesture is recognized.
override func didMove(to view: SKView) {
// Create the gesture recognizer for panning
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanFrom))
// Add the gesture recognizer to the scene's view
self.view!.addGestureRecognizer(panGestureRecognizer)
}
#objc func handlePanFrom(_ recognizer: UIPanGestureRecognizer) {
// This function is called when a pan gesture is recognized. Respond with code here.
}