iosswiftuipageviewcontrolleruiinterfaceorientationdevice-orientation

How to disable paging of UIPageViewController only for landscape orientation?


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:

If I take the approach of restricting to a 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?


Solution

  • 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
        }
    
    }