I have a collection view/scrollview where I'm adding my UILongPressGestureRecognizer. It works, but state .Began only gets fired together with state. Ended. That doesn't work for me as I want to also track the state .Changed to get the gesture.locationInView.
The problem I found is that it's conflicting with the Scroll gesture of the collection view .
If I set scrollEnabled to false, everything works as expected.
In my viewDidLoad I have
let longPressedGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(PhotosViewController.Scrubber(_:)))
self.scrubberCollectionView.addGestureRecognizer(longPressedGestureRecognizer)
longPressedGestureRecognizer.minimumPressDuration = 0.6
longPressedGestureRecognizer.delegate = self
self.scrubberCollectionView.addGestureRecognizer(longPressedGesture)
I also tried adding these to try to make one gesture fail when the pan gesture is enabled… (and I tried the shouldRecognizeSimultaneouslyWithGestureRecognizer as well.
longPressedGestureRecognizer.requireGestureRecognizerToFail(scrubberCollectionView.panGestureRecognizer)
func overridePan(gestureRecognizer: UILongPressGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIPanGestureRecognizer ) -> Bool {
return (gestureRecognizer == longPressedGesture && otherGestureRecognizer == self.scrubberCollectionView.panGestureRecognizer )
}
…to no avail.
For the main function I have
func Scrubber(gesture: UILongPressGestureRecognizer) {
if (gesture.state == .Began ) {
print("Began")
}
else if (gesture.state == .Changed) {
print("Changed")
}
else if (gesture.state == .Ended){
print("Ended")
}
}
When I long press I get no events on touch down, then on touch up I get these results:
Began
Ended
The actual long press is working, if I tap then the Scrubber function is not called, it's just firing both Began and Ended on the Ended state.
And added these things to viewDidLoad to try to delay the touch of the scrollview and even cancel it altogether…
self.scrubberCollectionView.delaysContentTouches = true
let longPress = UILongPressGestureRecognizer()
func gestureRecognizer(gestureRecognizer: UILongPressGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if(touch.view == scrubberCollectionView && gestureRecognizer == longPress){
return true
}else{
return false
}
}
…and nothing.
I figured it out.
I was adding the shouldRequireFailureOfGestureRecognizer inside viewDidLoad. It needs to be outside it.
In order to place it outside and make it work, you need to create your gesture variables (in my case "longPressedGesture") as optionals also outside viewDidLoad, like:
var longPressedGesture : UILongPressGestureRecognizer?
then inside viewDidLoad you create the gestures
longPressedGesture = UILongPressGestureRecognizer(target: self, action: #selector(PhotosViewController.Scrubber(_:)))
longPressedGesture.delegate = self
self.scrubberCollectionView.addGestureRecognizer(longPressedGesture)
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'm using a UIView with a UITapGestureRecognizer.
On the other hand I have a tap recognizer on ViewController's container view,
Now when i tap on my inner view, it calls handler,
BUT when I tap on for example 40 upper points of it, it doesn't work and container's tap recognizer fires!!!, actually i'm sure it's inside the UIView, i'm sure as I changed it's background to a different color, and on the other hand, there isn't any other views on this view, too!
It's really annoying, i've tested everything, hiding all views, disabling other recognizers and so on! And it's not my first time using this U I TAP RECOGNIZER :///
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewController.dismissKeyboard(_:))))
viewWrite.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewController.viewPressed(_:))))
EDIT::
I found that even when i touch here on my UITextView, the main container's tap event loads!! but other points of view, no! ::
You can try implementing UIGestureRecognizerDelegate
eg.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if (gestureRecognizer == recoginiser1 && wrongview1) return false
if (gestureRecognizer == recoginiser2 && wrongview2) return false
else return true;
}
wrongview means something like this.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if (gestureRecognizer == recognizer1){
let point = touch.locationInView(self.view)
if CGRectContainsPoint(textView.frame, point) == true {
return false
}
}
return true
}
Background:
I used to have a touch gesture detector on a view, and it worked fine.
Then my app grew, and now I need to add that same gesture detector to 3 views.
The Problem:
The gesture detector is never called when I add it to a views, that have been attached to a parent view with ".addSubview"
The Code:
parentView.addSubview(waveview)
parentView.addSubview(waveviewCap)
parentView.addSubview(waveviewCapBG)
let singleFingerDTap:UITapGestureRecognizer=UITapGestureRecognizer.init(target: self, action: "handleWaveviewTap")
singleFingerDTap.numberOfTapsRequired = 1;
self.waveview!.addGestureRecognizer(singleFingerDTap)
self.waveviewCap!.addGestureRecognizer(singleFingerDTap)
self.waveviewCapBG!.addGestureRecognizer(singleFingerDTap)
Please add UIGestureRecognizerDelegate in your parent view.And update the "shouldReceiveTouch" delegate method.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if touch.view == waveview || touch.view == waveviewCap || touch.view == waveviewCapBG {
return true
}
return false
}
Hope its working...
I would like to assign a unique behaviour for two-finger swipe on WKWebView.
Referring to this site, I wrote codes as below. It worked, but scrolling on webview got extremely slow.
Is there any better way to do it by avoiding slow scrolling?
let doubleSwipeGestureRecognizer = UISwipeGestureRecognizer.init(target: self, action: "doubleSwiped2")
doubleSwipeGestureRecognizer.numberOfTouchesRequired = 2
doubleSwipeGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Up
self.webView!.addGestureRecognizer(doubleSwipeGestureRecognizer)
for gesture in self.webView!.scrollView.gestureRecognizers!{
let gestureClass = gesture.classForCoder
let gestureName = NSStringFromClass(gestureClass)
print(gestureName)
if gestureName.containsString("Swipe"){
// do nothing
} else {
gesture.requireGestureRecognizerToFail(doubleSwipeGestureRecognizer)
}
}
Perhaps this codes suits for your demand.
First, you set self to delegate of recognizer.
doubleSwipeGestureRecognizer.delegate = self
Second, you writes a method of UIGestureRecognizerDelegate in self class.
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if otherGestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer.numberOfTouches() == 2 {
return true
}
return false
}
Third, you delete 'for' sentence.
for gesture in self.webView!.scrollView.gestureRecognizers! {
…
}