0
votes

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!

2
"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." Show us! Sorry for the blunt comment, but seriously? How can you expect us to help (much less know what's going on) when you give use nothing of technical value to go on? - dfd
Making sure I do what I preach.... detailed questions about the specifics that I think is lacking. (1) Obviously you are using gestures somehow. Well, how? Through IB? In code? Remember, we'd like to reproduce the issue you are facing. (2) What is the view hierarchy? Just a guess - you've given us nothing more - but is this interfering with the receiver of the gesture? (3) What delegate code - both presenting and being - is being implemented? Bottom line, what exactly can you give us to help us help you? - dfd
@dfd Hey, thanks for getting back to me, I've added code re: your first comment, I'll try and answer your second in the comments. - user8100252
The view hierarchy is as follows: Navigation Controller containing a Table View Controller. Interaction with the TVC pushes a Page View Controller on top of the TVC in the navigation stack. The Page View Controller comes built by Apple containing a scroll view to obviously page between views. This is the scrollview that I have added my additional edge pan gesture to. The scrollview contains a range of prebuilt gestures. My aim is to preference my edge pan over these prebuilt gestures when there is a conflict. I can see that there is a conflict from my implementation of shouldRequireFailureOf - user8100252
cont: as both my edge gesture and one of the scroll views gesture are passed as arguments. The implementation of this function has not affected the issue because it requires the gesture to fail rather than solving the conflict when it first occurs. Also, just for further reference, the edge pan is being added to pop back from the PVC to the TVC in the navigation stack. - user8100252

2 Answers

1
votes

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)
1
votes

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.