Suyash Gupta
Suyash Gupta

Reputation: 87

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.

Upvotes: 0

Views: 1634

Answers (3)

hohteri
hohteri

Reputation: 184

If your UIPageViewController is in a container view, you need to pass the UINavigationController or handle it inside the container's view controller:

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
    self.dataSource = self
    for view in view.subviews
    {
        if let scrollView = view as? UIScrollView
        {
            scrollView.panGestureRecognizer.require(toFail: self.parentNavController!.interactivePopGestureRecognizer!);
        }
    }
    self.parentNavController!.interactivePopGestureRecognizer!.delegate = self
}

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return self.parentNavController?.topViewController != self.parent || currentIndex == 0
}

In container view controller:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch (segue.identifier) {
        case "pageview_embed":
        let vc = segue.destination as! MyPageViewController
        vc.parentNavController = self.navigationController
        ...
        break
        ...
    }
    ...
}

Upvotes: 0

mmklug
mmklug

Reputation: 2412

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.

Upvotes: 3

milo
milo

Reputation: 477

By design UIPageViewController is not aware of the current view controller index. You can create a new instance of UIViewController every time pageViewController(_:viewControllerAfter:) is called and you will have infinite number of pages forward.

I suspect that you provide some kind of data source to UIPageViewController, either by it's data source or by setViewControllers(_:direction:animated:completion:). So it's your responsibility to track the index of the currently displayed UIViewController.

You can check what view controller is being shown by accessing first element of the previousViewControllers in UIPageViewController's delegate:

func pageViewController(_ pageViewController: UIPageViewController, 
              didFinishAnimating finished: Bool, 
         previousViewControllers: [UIViewController], 
             transitionCompleted completed: Bool)

Upvotes: 0

Related Questions