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
Related
I have a problem where I have a UIScrollView and a Header (UIView) inside my main View and my Header is over my UIScrollView as such:
UIView.
|
|- UIScrollView.
|
|- Header. (UIView)
I want my header to be able to detect taps on it, but I also want my scroll view to be able to scroll when I drag over my Header which right now it is not possible because my Header is over it and is blocking the scroll.
To sum up, I want my Header to detect taps but forward scrolls to my UIScrollView.
To tackle this problem I tried multiple things, and here are some of them:
Adding a UIPanGestureRecognizer to my Header so it is able to detect dragging
Adding a UITapGestureRecognizer to my Header so it is able to detect tapping
Setting isUserInteractionEnabled = false when dragging begins so the gesture can be passed to the next UIResponder which in this case is my UIScrollView
Setting isUserInteractionEnabled = true once my dragging has finished so it can again detect tapping
This is the code snippet:
override func viewLoad() {
myScreenEdgePanGestureRecognizer = UIPanGestureRecognizer(target: self, action:#selector(handlePan))
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
headerView.addGestureRecognizer(myScreenEdgePanGestureRecognizer)
headerView.addGestureRecognizer(tapGestureRecognizer)
}
#objc func handlePan(_ sender: UITapGestureRecognizer){
print("dragging")
if headerView.isUserInteractionEnabled{
headerView.isUserInteractionEnabled = false
}
if sender.state == .began {
} else if sender.state == .ended {
headerView.isUserInteractionEnabled = true
}
}
#objc func handleTap(_ sender: UITapGestureRecognizer){
print("tapped")
}
At this point I see how dragging and tapping are being detected just fine, but for some reason isUserInteractionEnabled = false seems to not be changing how the view is behaving. This code is acting as isUserInteractionEnabled is always true no mater what.
Things that I have also tried besides this:
overriding the hitTest function inside UIButton
overriding touchesBegan, touchesMoved, touchesEnded methods overriding next
setting the variable to return ScrollView as the next UIResponder
setting the isExclusiveTouch method in UIButton
changing the isUserInteractionEnabled in every way possible
I was struggling with this problem too, you should try to use methods of UIGestureRecognizerDelegate which allows you to handle simultaneous gestures.
Connect your gesture recognizers delegates e.g. tapGestureRecognizer.delegate = self
Make your ViewController conform this protocol e.g.
extension YourViewController: UIGestureRecognizerDelegate {}
Implement this function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true }
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
}
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 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
}
I have a collection view of bars that can slide up and down. Each cell in the collection view is using a UIPanGestureRecognizer to control the blue bar sliding up and down. The collection view does not scroll here.
There is an "edit mode" which disables the pan gesture controlling the bars. The hope here is that in "edit mode", the collection view can then scroll left and right.
My attempt at doing this was to disabled the pan gesture in each of the cells. I also tried using the UIGestureRecognizerDelegate methods to try to disable touches and fail the cell's pan gesture in favor of the collection view's pan gesture. It seems that the collection view's pan gesture is not forwarded to any of the cell's gesture delegate calls.
These delegate calls got me the closest (delegate for cell's pan gesture):
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return !self.editMode // bar doesn't need to pan in edit mode
}
With this implemented, I could pan the collection view if I started the pan in the white space between cells. Starting a pan on a cell would not do anything, though.
EDIT: I uploaded a sample project of the issue to github.
If I understand this correctly, I think the problem could be as follows:
Your UICollectionView will listen for pan gestures. So far so good.
In KBHBarSlider you add UIPanGestureRecognizer for you sliders. They have "priority" over the underlaying UICollectionView.
This actually makes sense as your BarSliders are above the CollectionView. My best take would be to let the BarSliders add the UIPanGestureRecognizer when needed, and have them completely removed when editing.
So first try and remove the gestureRecognizer you added in VOLBarSliderCell.
Then in KBHBarSlider add something like: (and make sure your init calls addPanRecognizer() instead of setup())
func addPanRecognizer() {
let panGesture = UIPanGestureRecognizer(target: self, action: "viewDidPan:")
self.gestureRecognizers = [panGesture]
}
func removePanRecognizer() {
self.gestureRecognizers = []
}
It is now possible to add and remove the gestures on demand, do so from your edit get/set overrides in VOLBarSliderCell:
var editing: Bool = false {
didSet {
if !self.editing {
self.barSlider.addPanRecognizer()
// Removed code, to keep this example short...
} else {
self.barSlider.removePanRecognizer()
// Removed code, to keep this example short...
}
}
}
Try adding this code at VOLBarSliderCell:29:
self.panGesture.enabled = !self.editing
And remove:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if self.editing && gestureRecognizer == self.panGesture {
return false
}
return true
}
To be fair your problem was not clear to me, so I gave a try as an answer anyway since I cannot comment yet to request clarifications.
I understood you wanna scroll your UICollectionView while you are in edit mode. So that's what the code above does. If you wish to disable scrolling altogether UICollectionView while NOT editing then you can use scrollEnabled = false on the UICollectionView.
Clarify a bit better what is the problem you are trying to solve. That way you might be able to bring precise answers.
You're on the right track. Keep a reference to your leftRight gestureRecognizer. Then in shouldReceiveTouch, check if editing and the gestureRecognizer is your leftRight recognizer
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if(self.editing && gestureRecognizer != self.leftRightRecognizer){
return 0;
}
return 1;
}