4

I got an PageViewController which loads two "child "ViewControllers in order to let the user "swipe" through them. I don't want this swipe gesture , but instead I want to have a function inside my ViewController which allows me to use setViewControllers in the PageViewController.

I tried using protocols but even that didn't work out.

I would realy appreciate any help or suggestions on how I could accomplish that. Thanks!

Phil Legat
  • 43
  • 1
  • 3

3 Answers3

7

To access setViewControllers from your child view controllers, you will need your child view controllers to be aware of their parent PageViewController. To do so, start by making a Protocol (I know you've said you've tried Protocols, but please please see my method through). This Protocol will ensure that every child view controller has a reference to the parent PageViewController.

    protocol PageObservation: class {
        func getParentPageViewController(parentRef: PageViewController)
    }

Ensure that your child view controllers adhere to the PageObservation Protocol.

    class Child1ViewController: UIViewController, PageObservation {
        var parentPageViewController: PageViewController!
        func getParentPageViewController(parentRef: PageViewController) {
            parentPageViewController = parentRef
        }
    }

    class Child2ViewController: UIViewController, PageObservation {
        var parentPageViewController: PageViewController!
        func getParentPageViewController(parentRef: PageViewController) {
            parentPageViewController = parentRef
        }
    }

In your PageViewController, as you create each child view controller, cast them to the PageObservation type and pass a reference of the parent PageViewController. I use an array called orderViewControllers to create my pages. My UIPageViewControllerDataSource delegate methods uses it to know which pages to load but that is irrelevant to this example, I just thought I'd let you know in case you have a different way of creating your pages.

    class PageViewController: UIPageViewController {
        var orderedViewControllers: [UIViewController] = []


        //creating child 1
        //i am using storyboard to create the child view controllers, I have given them the identifiers Child1ViewController and Child2ViewController respectively
        let child1ViewController =  UIStoryboard(name: "Main", bundle: nil) .
        instantiateViewController(withIdentifier: "Child1ViewController")
        let child1WithParent = child1ViewController as! PageObservation
        child1WithParent.getParentPageViewController(parentRef: self)

        orderedViewControllers.append(child1ViewController)

        //creating child 2
        let child2ViewController =  UIStoryboard(name: "Main", bundle: nil) .
        instantiateViewController(withIdentifier: "Child2ViewController")
        let child2WithParent = child2ViewController as! PageObservation
        child2WithParent.getParentPageViewController(parentRef: self)

        orderedViewControllers.append(child2ViewController)
    }

Now inside your child view controllers, you have access to setViewControllers. For example, if I want to call setViewControllers in the child1ViewController, I have created a func called accessSetViewControllers() where I access the setViewControllers:

    class Child1ViewController: UIViewController, PageObservation {
         var parentPageViewController: PageViewController!
        func getParentPageViewController(parentRef: PageViewController) {
            parentPageViewController = parentRef
        }

        func accessSetViewControllers() {
             parentPageViewController.setViewControllers( //do what you need )
        }
    }

On a side note, despite what other answers above have said, you can set dataSource to whatever you like. I sometimes set dataSource to nil to prevent the user from swiping away from a screen before doing something and then add the dataSource back to allow them to continue swiping.

gwinyai
  • 2,560
  • 2
  • 15
  • 17
  • Thanks for your answer. But what is "pageViewController" in your code? You are using it while creating the child controllers. – Phil Legat Feb 25 '17 at 01:21
  • I tried your solution and everything seems to work! By far the best answer found online! However, I have got one more question: Let's say I have 3 Child View Controllers and each of them is embeded in their own Navigation Controller. How to I "create the child controllers" then? Only referring to them with the Navigation Controller - Storyboard - ID didn't work. Do I have to change something else? Best regards, and big Thank you! – Phil Legat Feb 26 '17 at 01:37
  • If I understand you correctly, you have a PageViewController with three children but each of these children has a Navigation Controller of their own, right? You do not need to reference the Navigation Controller to create the children. You just need to give the child an identifier and instantiate it from that, the child will be aware it has a Nav controller on its own. For a child with an identifier "child" but embedded in a nav controller you would do this: let childVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "child") as! ChildViewController – gwinyai Feb 26 '17 at 12:45
  • I tried your suggestions today but I can't get it to run properly. App crashes with Signal 1 Sigabrt or strange unwrapping errors.. Could you give me a working example? I would really appreciate it. Sorry for my ongoing questions but I am fairly new to Swift and still try to learn a lot! – Phil Legat Feb 27 '17 at 13:06
  • Would be glad to help but perhaps I'm the one who is not understanding what you want to achieve. I think it would be best if you put this as a question on Stack where I or anyone could try answer it. Some tips: add a diagram if possible, add the code you've already tried but did not work, mention any solutions on Stack that did not work for you. – gwinyai Feb 28 '17 at 11:22
  • Sorry for my late response, was quiet busy terminating bugs. I build an working solution based on your code and it really works! Thank you so much, I learned a lot and wish you the best! – Phil Legat Mar 06 '17 at 02:21
  • Great! Helped me too! – angryip May 03 '19 at 13:23
  • Works great! Thanks – mikemike396 May 09 '19 at 18:01
0

Don't set dataSource. When it's nil, then gestures won't work.

https://developer.apple.com/reference/uikit/uipageviewcontroller

When defining a page view controller interface, you can provide the content view controllers one at a time (or two at a time, depending upon the spine position and double-sided state) or as-needed using a data source. When providing content view controllers one at a time, you use the setViewControllers(_:direction:animated:completion:) method to set the current content view controllers. To support gesture-based navigation, you must provide your view controllers using a data source object.

Dmitry
  • 2,837
  • 1
  • 30
  • 48
  • Yeah, I have read about that. The problem is, that I don't know how to call "setViewControllers" from my child ViewControllers. Anyways, thanks for replying – Phil Legat Feb 24 '17 at 01:30
  • It would be better if you create a delegate protocol with methods like `goBack`/`goForward` and add a weak property for the delegate to your child VCs. After that make your parent VC the delegate to your child VCs. In this case you don't need to call `setViewControllers` from children directly and they won't know anything about Page VC – Dmitry Feb 24 '17 at 01:34
  • I will try that! Maybe it works, if it does so, you are truly a life safer! – Phil Legat Feb 24 '17 at 01:36
0

Simplistic approach... remove the inbuilt gesture recogniser in viewDidLoad of pageViewController:

for view in self.pageViewController!.view.subviews {
   if let subView = view as? UIScrollView {
       subView.scrollEnabled = false
   }
}

Then add your own gesture below it. i just happened to be working with double tap at the moment but you could make it swipe left, swipe right easy enough:

let doubleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap))
doubleTap.numberOfTapsRequired = 2
doubleTap.delaysTouchesBegan = true
self.addGestureRecognizer(doubleTap)

and the gesture function with your code:

func didDoubleTap(gesture: UITapGestureRecognizer) {

//... stuff

}
WanderingScouse
  • 281
  • 1
  • 3
  • 16