I am trying to determine if there is a means of programmatically setting a gesture recognizer state, to force it to begin prior to it actually detecting user input.
For example, I am adding a pan gesture recognizer to an image when a long press is detected, like so;
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: "longPressed:")
myImage.addGestureRecognizer(longPressRecognizer)
func longPressed(sender: UILongPressGestureRecognizer) {
let mainWidth = UIScreen.mainScreen().bounds.width
let mainHeight = UIScreen.mainScreen().bounds.height
let myView: UIView(frame: CGRect(x: 0, y: 0, width: mainWidth, height: mainHeight)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
myView.addGestureRecognizer(gestureRecognizer)
self.view.addSubview(myView)
}
In the handlePan() function, I'm able to determine when the pan starts and ends;
func handlePan(gesture: UIPanGestureRecognizer) {
if gesture!.state == UIGestureRecognizerState.Began {
print("Started pan")
}
if gesture!.state == UIGestureRecognizerState.Ended {
print("Ended pan")
}
}
My issue is that, to detect when the gesture started, the user has to (1) long press on the image, (2) release their finger, (3) press and hold and start panning. Ideally, I'd like to have the user (1) long press on the image, (2) start panning.
To accomplish this, I'm imagining I need to figure out a way to "trick" things into believing that the pan gesture already began.
note: In practicality, there is more complexity than what's presented here, which is why I need to add a subview with the pan gesture, rather than just adding the pan gesture to the image directly.
What you want to do is add both gesture recognizes up front, set their delegates to your class, allow them to recognize simultaneously (using the below method), and only use the data from the pan when the long press has successfully been recognized.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Related
I have got an UITableView with a custom TableViewCell. I use a pan gesture for recognizing the positions while moving my finger to the left and to the right. On basis of the finger position I change some values in the labels in this TableViewCell. This works really great. But suddenly I can not scroll the TableView up and down. I already read the reason. Swift can not work with two gesture recognizers at the same time. And I found many examples of people how have nearly the same problem. I tried many of them but I can not fix my problem. I use Swift 5. Could you please describe a bit more precise how to fix my problem? Many thanks
import UIKit
class TVCLebensmittel: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
self.addGestureRecognizer(gestureRecognizer)
}
#IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
if gestureRecognizer.state == .began {
let translation = gestureRecognizer.translation(in: self)
// Put my finger on the screen
} else if gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self)
// While moving my finger ...
} else if gestureRecognizer.state == .ended {
let translation = gestureRecognizer.translation(in: self)
// Lift finger
}
}
...
}
The solution is to insert the pan gesture to the tableview and not to the tableviewcell. So I can listen to the left and right pan and also the up and down movement of the tableview.
I just share my approach. link It works very well. I needed custom swipe design while perform delete. Try it. If you need more information feel free to ask. Check it if you like.
This gestureRecognizerShouldBegin let you to use scroll while using UIPanGestureRecognizer.
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer.isKind(of: UIPanGestureRecognizer.self)) {
let t = (gestureRecognizer as! UIPanGestureRecognizer).translation(in: contentView)
let verticalness = abs(t.y)
if (verticalness > 0) {
print("ignore vertical motion in the pan ...")
print("the event engine will >pass on the gesture< to the scroll view")
return false
}
}
return true
}
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 a UIScrollView. I put a tap gesture to it as this:
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapped(_:)))
self.tapGesture.delegate = self
self.tapGesture.numberOfTapsRequired = 1
self.tapGesture.numberOfTouchesRequired = 1
self.tapGesture.cancelsTouchesInView = false
self.tapGesture.delaysTouchesBegan = false
self.tapGesture.delaysTouchesEnded = false
self.scrollView.addGestureRecognizer(self.tapGesture)
This works fine, except when the scrollview is scrolling (scrolling animation is happening, not user dragging), tap gesture is ignored.
How I am animating the scroll view:
UIView.animate(withDuration: 0.3, delay: 0.0,
options:[.beginFromCurrentState, .curveEaseInOut], animations:
{
self.scrollView.contentOffset = CGPoint(x:self.scrollView.contentOffset.x, y:yOffset)
}, completion: nil)
This scrollview is scrolling most the time and I am trying to get it to recognize the tap gesture while scroll view is animating scrolling ....
Take a look at the UIGestureRecogniserDelegate functions.
You should be able to specify that both the pan and tap gestures can be recognised at the same time with the following function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return (gestureRecogniser is UIPanGestureRecogniser || gestureRecogniser is UITapGestureRecogniser) && (otherGestureRecognizer is UIPanGestureRecogniser || otherGestureRecognizer is UITapGestureRecogniser)
}
Note: Ensure your class conforms to the UIGestureRecogniserDelegate protocol and you set the gestures delegate so self.
This should work, but I am unable to fully test it right now.
UPDATE:
If you are trying to recognise a tap during an animation you will likely need to use the UIViewAnimationOptions.AllowUserInteraction option in the options of UIView.animateWithDuration. Used this other answer as a source
I have a small view that I want to be moved around the screen and tapable. So I add a UIPanGestureRecognizer to move it, and a UITapGestureRecognizer to receive tap events like so:
let panner = UIPanGestureRecognizer(target: self, action: #selector(panDidFire(panner:)))
playerViewController.view.addGestureRecognizer(panner)
let tapper = UITapGestureRecognizer(target: self, action: #selector(viewTapped(tapper:)))
playerViewController.view.addGestureRecognizer(tapper)
And I create the actions in the same file
func viewTapped(tapper: UITapGestureRecognizer){
fadeInButtons()
}
func panDidFire(panner: UIPanGestureRecognizer) {
let offset = panner.translation(in: view)
panner.setTranslation(CGPoint.zero, in: view)
var center = playerViewController.view.center
center.x += offset.x
center.y += offset.y
playerViewController.view.center = center
}
The panDidFire() function is called when the user pans, but the viewTapped() function is not called at all.
Is there a trick to this? What am I doing wrong? Is there another way of going about this?
I am using Xcode 8 with swift 3
You have to set to your recognizers to work with other gesture recognizer on the view. Please use method of UIGestureRecognizerDelegate
gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool
{
return true
}
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
}