UILongPressGestureRecognizer and UIScreenEdgePanGestureRecognizer - swift

I'm using a UIScreenEdgePanGestureRecognizer to change views and in one of the views I can use a UILongPressGestureRecognizer (with minimum duration of 0) to move a row in the table. The problem is, this press gesture is at the edge of the screen so I have to configure the delegates for them to work properly.
The delegate of the press gesture has been set:
override func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer is UIScreenEdgePanGestureRecognizer
}
This works fine in the simulator (since a housepainter is much more accurate) but on the device itself it's less reliable. I can change views without problems but moving the rows can still be a bit tricky.
So I changed it to:
override func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return gestureRecognizer is UILongPressGestureRecognizer && otherGestureRecognizer is UIScreenEdgePanGestureRecognizer
}
Now both are working fine but obviously both of the gestures are working at the same time which I don't want to. I tried setting a condition to fail the press gesture if the velocity of the x axis of the pan gesture is bigger than 0, but by then the press gesture has already started.

One thing you can do is add a tag to each gesture recognizer and do something like:
func doSomething(sender: UIGestureRecognizer) {
if sender.tag == 1 {
// do this
} else {
// do that
}
}

Related

Can't find which view triggered the tap gesture recognizer in my custom view

I know a lot of questions are answered for the issues like this but in my case, the common answers do not work.
I have a custom view which is a small view that appears above the active textfield to show the clipboard data so if the user wants that data in that textField, taps on my custom view so the clipboard text automatically pastes into that textField.
everything works fine when there is a single textField on the page. but if I add another textField, this custom view can't detect the correct textField to paste the text into it.
here is my code inside my customView to detect current textField
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
tap.cancelsTouchesInView = true
tap.delegate = self
window?.addGestureRecognizer(tap)
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.frame.contains(subviews.first!.frame) {
tapComple?(true)
} else {
tapComple?(false)
}
return true
}
I know that already other answers on the stackoverflow are saing to use this:
if touch.view == self
But it is not working in my case...
The demo project to see everything.
You need to detect if the rectangle contains the point which you touched. You can achieve that with the code below.
Change your gestureRecognizer(_:shouldRecieve:) method to the following:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if subviews.first?.frame.contains(touch.location(in: subviews.first)) == true {
print(true)
tapComple?(true)
} else {
print(false)
tapComple?(false)
}
return true
}
Result:
You don't need to do any calculations or listening gesture recognizer delegate.
Instead of that you can add tap gesture recognizer on your own view, on on the window. So all taps it receive - will be correct ones.
Also right now you're adding your view as subview to text field. This is really a bad practice and will stop this view from receiving taps.
Instead of that I suggest you adding it to to UIViewController view. To do so you need to pass two views into your LContextView: superview and anchorView on which you'll bind your constraints
private func configView(superView: UIView, anchorView: UIView, size: CGSize) {
removePreviusContexes(superView: superView)
superView.addSubview(self)
backgroundColor = .white
translatesAutoresizingMaskIntoConstraints = false
widthAnchor.constraint(equalToConstant: size.width).isActive = true
heightAnchor.constraint(equalToConstant: size.height).isActive = true
centerXAnchor.constraint(equalTo: anchorView.centerXAnchor).isActive = true
centerYAnchor.constraint(equalTo: anchorView.centerYAnchor, constant: -50).isActive = true
...
private func addTapGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction))
addGestureRecognizer(tap)
}

UISlider and Pan Gesture Recognizer do not mix

As asked here: Pan gesture interferes with UISlider and Gesture problem: UISwipeGestureRecognizer + UISlider I cannot get UISlider to work with a pan gesture where in my navigation controller I have a class that has a "back" button capability.
The answers mention that I should add a delegate to my class as such
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let view = touch.view, view == slider {
return false
}
return true
}
However, this method never gets called. I have tried about 15 ways to set the delegate for this class, but I honestly have no clue what to set at the delegate.
let myScreenEdgePanGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action:nil)
myScreenEdgePanGestureRecognizer.delegate = self
Nothing to this effect works. Could someone clearly explain how I can fix this problem. I have tried every approach on the first 2 pages of google. Thank you.
If you see this code more precisely, then code is checking class as a UISlider not object of UISlider.
So in swift you have to write like this
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let view = touch.view
if view is UISlider { // check class as a UISlider
return false
}
return true
}
I didn't run and check my code, if any editing required then do and if you need any help then add comment.
Edit
For UIScreenEdgePanGestureRecognizer, check this post :
https://www.hackingwithswift.com/example-code/uikit/how-to-detect-edge-swipes
Try to declare edges
In Swift it will looks like
let myScreenEdgePanGestureRecognizer = UIScreenEdgePanGestureRecognizer(target: self, action:nil)
myScreenEdgePanGestureRecognizer.delegate = self
myScreenEdgePanGestureRecognizer.edges = .left //.right, .top, .bottom

Ordering gesture recognisers

I was wondering if I could get some clarification on an issue because I have found Apple's documentation to be very unclear. I have added an edge pan gesture to a UIScrollView (or more accurately the scrollview of a UIPageViewController) and I have found that the swipe/pan gestures of the scrollview clash with the edge pan gesture that I have added.
edit: As requested below, here is the code I have used to implement the gesture on the scroll view and the delegate functions that I have used.
PageViewController VDL:
override func viewDidLoad(){
super.viewDidLoad()
self.dataSource = self
for eachSubView in self.view.subviews {
if String(describing: type(of: eachSubView)) == "_UIQueuingScrollView" {
let leftEdge = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromLeft(_:)))
leftEdge.edges = .left
leftEdge.delegate = self
eachSubView.addGestureRecognizer(leftEdge)
}
}
}
Handle Swipe From Left Function:
func handleSwipeFromLeft(_ gesture: UIScreenEdgePanGestureRecognizer) {
let percent = gesture.translation(in: gesture.view!).x / gesture.view!.bounds.size.width
if gesture.state == .began {
interactionController = UIPercentDrivenInteractiveTransition()
if self.navigationController!.viewControllers.count > 1 {
self.navigationController?.popViewController(animated: true)
} else {
dismiss(animated: true)
}
} else if gesture.state == .changed {
interactionController?.update(percent / 4.8)
} else if gesture.state == .ended {
if percent > 0.2 && gesture.state != .cancelled {
interactionController?.finish()
} else {
interactionController?.cancel()
}
interactionController = nil
}
}
GestureRecogniserDelegate:
extension ArticleViewPageController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if String(describing: type(of: gestureRecognizer)) == "UIScreenEdgePanGestureRecognizer" {
return false
} else {
return true
}
}
}
I have been reading Apple's documentation about this issue (Coordinating Multiple Gesture Recognisers, Preferring One Gesture Over Another) but their documentation has not helped. In the first of the two documents there is a section that reads:
To prevent the unintended side effects of the default recognition behavior, you can tell UIKit to recognize gestures in a specific order using a delegate object. UIKit uses the methods of your delegate object to determine whether a gesture recognizer must come before or after other gesture recognizers.
This is exactly what I want to achieve as I want to preference the edge swipe over the other gestures of the scrollview. However, that section goes on to talk about achieving this by implementing the UIGestureRecognizerDelegate method shouldRequireFailureOf which I have implemented but since the scrollview's pan gesture does not actually fail until after a finger is lifted, this does nothing to preference the edge gesture.
I have also implemented the shouldRecognizeSimultaneouslyWith method which does resolve the conflict but it also causes the scrollview to scroll during the edge pan.
I would love to be able to do as that excerpt says and have my gestures recognised in a specific order. Any help in achieving this would be very much appreciated.
Thanks!
This is a bit long shot, but to me it looks like that you should set your leftEdge recogniser to delay touch events and make table recogniser to require leftEdge to fail before it can take over.
leftEdge.delaysTouchesBegan = true
tableView.panGestureRecognizer.require(toFail: leftEdge)
For anyone trying to achieve this very niche thing in the future, I have found a work around.
I implemented the UIGestureRecognizerDelegate method shouldRecognizeSimultaneouslyWith like so:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Because I only implemented the delegate on my edge pan gesture (as shown above) it is only called when the edge pan is activated. This stopped the conflict between my edge pan and the scrollview's pan but introduced the problem of the PageViewController paging whilst my edge pan caused a pop back in the navigation stack. To counteract this, I added the following code to my edge gesture function (again, as outlined above) to be called when the gesture state was .begun:
for eachSubView in self.view.subviews {
if String(describing: type(of: eachSubView)) == "_UIQueuingScrollView", let queueScrollView = eachSubView as? UIScrollView {
queueScrollView.isScrollEnabled = false
}
}
Finally, I added the same block to the viewDidAppear function in my PageViewController except I set queueScrollView.isScrollEnabled = true. This meant that even if the pop gesture was cancelled, paging between views would still work.
This isn't a fantastic solution but it does have the intended effect of prioritising the edge gesture over the pan gesture, just in a very inelegant way. If a better answer comes up, I will edit this post.

Handling Multiple GestureRecognizers

I've run into an issue understanding UIGestureRecognizers. My goal right now is to have a set of GestureRecognizers to do different tasks, for example:
override func viewDidLoad() {
mainScene = GameScene(size: self.view.bounds.size)
main = view as! SKView
mainScene.panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(shiftView(recognizer:)))
main.addGestureRecognizer(mainScene.panRecognizer)
mainScene.tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(testTap(recognizer:)))
main.addGestureRecognizer(mainScene.tapRecognizer)
mainScene.pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(zoomView(recognizer:)))
main.addGestureRecognizer(mainScene.pinchRecognizer)
This is my game View Controller where I handle actions such as panning around a map, zooming, and tapping on map tiles. But I also want to be able to move sprites with a UITapGestureRecognizer so I also created this in my GameScene:
if startGame == true{
self.startGame()
for node in (self.tempGameBoard.landShipLayer.children as? Array<landship>)! {
node.landShipInteraction = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
parentViewController.view.addGestureRecognizer(node.landShipInteraction)
}
}
The landShip in this case is representative of a sprite on screen that I would like to interact with via gesture recognizers.
My issue is that if I add this second set of recognizers, the tapping action becomes completely unresponsive. I can still zoom and pan, but the tapping behaviors I expect on my map tiles do not occur. I feel as though I am missing some understanding of how the gesture recognizers work.
Any ideas?
Thanks!
The UIGestureRecognizerDelegate has a special function managing simultaneous recognition of several gestures on the same object, that will do the trick.
1) Set your UIViewController to conform UIGestureRecognizerDelegate
2) Implement the following function:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if (gestureRecognizer == mainScene.panRecognizer || gestureRecognizer == mainScene.pinchRecognizer) && otherGestureRecognizer == mainScene.tapRecognizer {
return true
}
return false
}
In this particular example we allow the tap gesture to get triggered simultaneously with panning and pinching.
3) Then just assign the delegates to the pan and pinch gesture recognizers:
override func viewDidLoad() {
// your code...
// Set gesture recognizers delegates
mainScene.panRecognizer.delegate = self
mainScene.pinchRecognizer.delegate = self
}

Simple Swipe in Swift - xCode 6

I'm trying to make a simple swipe up gesture, I dragged the Swipe Gesture Recogniser over a UIImage, I then Ctrl button drag the Swipe Gesture to my swift file and create the following Action: -
#IBAction func swipeDice(sender: UISwipeGestureRecognizer) {
//Test display
testLabel.text = "Zing"
}
The app builds and runs successfully however when I test the swipe gesture it doesn't seem to do anything.
Is that all the code I need for the gesture to run?
How do I make it recognise a 2 finger swipe gesture?
You need to enable User Interaction for the UIImage that you added the UIGestureRecognier to:
Open the Attributes Inspector for the UIImage and tick User Interaction Enabled:
If you created the Swipe Gesture Recognizer in IB you can setup the gesture through the right window.
If you have a conflict between several gestures you can handle this through the UIGestureRecognizerDelegate protocol.
Add the following function to your code:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}