0
votes

I have a scroll transition style UIPageViewController that needs to disable paging only when device is in landscape orientation. But paging should be enabled in portrait orientation.

I have encountered similar questions here in SO but not my specific need. Some of them are:

How do I Disable the swipe gesture of UIPageViewController?

Disable Page scrolling in UIPageViewController

Disable/enable scrolling in UIPageViewController

Restrict UIPageViewController (with TransitionStyleScroll) pan gesture to a certain area

All of the above points to either completely disabling or restricting pan gesture to a certain area.

Now if I take the approach of completely disabling:

  • I will need to track device orientation change
  • Disable when orientation is set to landscape
  • Again enable when orientation is changed to portrait

If I take the approach of restricting to a certain area:

  • I will need to find that certain area

  • That certain area (described in previous point) needs to be calculated differently for portrait & landscape orientation

  • Certain area for portrait orientation needs to be the area of the whole UIPageViewController bounds

  • Certain area for landscape orientation needs to be a very minimum area (whose frame could be 0, 0, 1, 1) where user won't be able to perform pan operation. This frame calculation needs to be very precise because my UIPageViewController takes the whole bounds of the main screen in landscape orientation.

  • Then again may need to track device orientation change for different calculation of the certain area


There are some techniques where the authors suggest:

pvc.dataSource = nil // prevents paging

pvc.dataSource = `a valid dataSource object` // enables paging

So, manual enable + disable again. Track orientation change and enable/disable.

This isn't safe to use for my specific use case as there is a possibility of assigning data source multiple times.


There are other approaches which, I think, can't be modified to fit the use case.

Is there a shortcut way to achieve what I need?

1

1 Answers

0
votes

Answering to my own question as I've already achieved what I needed to.

Subclassing UIPageViewController is the easiest way. We have to find the underlying UIScrollView that is used by the page view controller to handle its pan gesture related work. We will add another UIPanGestureRecognizer to that internal scroll view. This pan gesture recognizer won't perform any action essentially but it will block the internal pan gesture recognizer to be recognized for landscape orientation only.

Sample implementation:

class CustomPageViewController: UIPageViewController, UIGestureRecognizerDelegate {

    override func viewDidLoad() {
        if let underlyingScrollView = view.subviews.compactMap({ $0 as? UIScrollView })
                                    .first {

            let pangestureRecognizer = UIPanGestureRecognizer()
            pangestureRecognizer.delegate = self
            underlyingScrollView.addGestureRecognizer(pangestureRecognizer)
            // at this point, the underlying scroll view will have two pan gesture
            // recognizer side by side. We have the control of our added pan gesture
            // recognizer through the delegate. We can conditionally recognize it or not
        }
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 
         shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) 
         -> Bool {
        // Returning true from here means, page view controller will behave as it is
        // Returning false means, paging will be blocked
        // As I needed to block paging only for landscape orientation, I'm just returning
        // if orientation is in portrait or not
        return UIApplication.shared.statusBarOrientation.isPortrait
    }

}