Search code examples
iosswipeuipageviewcontroller

How to pop UIPageViewController when swipe back from first page or 0 index?


I know the swipe is managed by UIPageViewController by default and there are callback methods which are called during this process like willTransitionTo, didFinishAnimating etc.

Is it possible to dismiss the UIPageViewController when we swipe back from first page or zeroth Index?

I checked and found none of the callback methods are called for this action.


Solution

  • I was able to achieve the intended effect as follows.

    1) Add the following properties to your UIPageViewController subclass:

    var scrollView: UIScrollView?
    var swipeBackPanGestureRecognizer: UIPanGestureRecognizer?
    

    2) Now add the following code to your UIPageViewController subclass' viewDidLoad method:

    scrollView = view.subviews.filter{ $0 is UIScrollView }.first as? UIScrollView
    
    if let scrollView = scrollView,
       let popGestureRecognizer = self.navigationController?.interactivePopGestureRecognizer,
       let targets = popGestureRecognizer.value(forKey: "targets") as? NSMutableArray {
        let panGestureRecognizer = UIPanGestureRecognizer()
        panGestureRecognizer.setValue(targets, forKey: "targets")
        panGestureRecognizer.delegate = self
        scrollView.addGestureRecognizer(panGestureRecognizer)
        swipeBackPanGestureRecognizer = panGestureRecognizer
    }
    

    Basically you are adding a new UIPanGestureRecognizer to the build in scrollView that takes over the target actions from the built in interactivePopGestureRecognizer, responsible for the swipe back (I found this tip here: https://stackoverflow.com/a/57487641/10060753).

    3) Finally you need to implement these two protocol methods. Make sure to add the following code below the last closing bracket of your UIPageViewController subclass and also to replace "YourPageViewController" with the appropriate subclass name:

    extension YourPageViewController: UIGestureRecognizerDelegate {
        func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
            guard let panRecognizer = gestureRecognizer as? UIPanGestureRecognizer,
                panRecognizer == swipeBackPanGestureRecognizer else {
                return true
            }
    
            guard let currentViewController = self.viewControllers?.first,
                pageViewController(self, viewControllerBefore: currentViewController) == nil else {
                return false
            }
    
            guard let gestureView = panRecognizer.view else {
                return true
            }
    
            let velocity = (panRecognizer.velocity(in: gestureView)).x
            return velocity > 0
        }
    
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            if gestureRecognizer == swipeBackPanGestureRecognizer && otherGestureRecognizer == scrollView?.panGestureRecognizer {
                return true
            } else {
                return false
            }
        }
    }
    

    The implementation of gestureRecognizerShouldBegin(_:) ensures that our swipe back gesture only gets active if we are a) on the first page (UIPageViewControllerDataSource viewControllerBefore == nil) and b) if we are swiping from left to right (velocity > 0).

    The implementation of gestureRecognizer(_: shouldBeRequiredToFailBy:) ensures that the built-in UIPageViewController panGestureRecognizer only gets triggered if our own swipe back gesture recognizer fails.