Reputation: 51
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!
Upvotes: 0
Views: 2099
Reputation: 51
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.
Upvotes: 1
Reputation: 1276
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)
Upvotes: 1