29

I have a UIPageViewController, which works fine when we swipe left or right to turn pages.

class ViewController: UIViewController, UIPageViewControllerDataSource {
...
}

Now I intend to provide Previous/Next button on the page too, so that pages can be turned by clicking on these buttons.

How can I trigger the swipe left/right behaviour OR turn pages programmatically?


Note

This is a question for Swift language, not Objective-C.

Community
  • 1
  • 1
Jasper
  • 8,440
  • 31
  • 92
  • 133
  • possible duplicate of [Is it possible to Turn page programmatically in UIPageViewController?](http://stackoverflow.com/questions/7208871/is-it-possible-to-turn-page-programmatically-in-uipageviewcontroller) – Ewan Mellor May 27 '15 at 18:47

6 Answers6

72

There's generally no need to fiddle around with page indexes and what not. Chances are you are already implementing a dataSource: UIPageViewControllerDataSource, which has all of what you need to get a previous or next ViewController to display.

Swift 5 Example:

extension UIPageViewController {

    func goToNextPage() {
       guard let currentViewController = self.viewControllers?.first else { return }
       guard let nextViewController = dataSource?.pageViewController( self, viewControllerAfter: currentViewController ) else { return }
       setViewControllers([nextViewController], direction: .forward, animated: false, completion: nil)
    }

    func goToPreviousPage() {
       guard let currentViewController = self.viewControllers?.first else { return }
       guard let previousViewController = dataSource?.pageViewController( self, viewControllerBefore: currentViewController ) else { return }
       setViewControllers([previousViewController], direction: .reverse, animated: false, completion: nil)
    }

}

This way you're guaranteed that the pages you're transitioning to are exactly what the PageViewController's built in gestures would trigger.

Sharad Chauhan
  • 4,821
  • 2
  • 25
  • 50
0x6A75616E
  • 4,696
  • 2
  • 33
  • 57
  • 3
    I'm having a tough time figuring how how to update `presentationIndexForPageViewController` using this your idea. On my `pageItemViewController`, I've got a variable declared called `itemIndex`. How would I set that within this extension? – Adrian Sep 05 '16 at 18:17
  • I used func goToNextPage() with timer - So how can I use this method to scroll to next page with timer? here is the link of my question https://stackoverflow.com/questions/45025088/change-pages-in-uipageviewcontroller-automatically-with-timer-in-swift-3?noredirect=1#comment77027842_45025088 – Saeed Rahmatolahi Jul 11 '17 at 06:36
  • Lifesaver!!! Thanks so much. I'm gonna post the Swift 3 version just for future reference. – Gligor Aug 14 '17 at 04:49
  • 1
    Nice work, demonstrates a gap in the UPPageViewController in my humble opinion. :) – William T. Mallard May 16 '18 at 23:55
  • 2
    Quick note: this method does not appear to fire the UIPageViewControllerDelegate(:didFinishAnimating, whereas it does get called when paging with gestures, more a bug than a feature I suspect. – William T. Mallard May 17 '18 at 16:17
25

The Swift 3 version of SuitedSloth's answer (with a small tweak to the animated parameter as I needed it to be animated by default, but still taking a parameter in the function) in case anyone needs it:

extension UIPageViewController {

    func goToNextPage(animated: Bool = true) {
        guard let currentViewController = self.viewControllers?.first else { return }
        guard let nextViewController = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) else { return }
        setViewControllers([nextViewController], direction: .forward, animated: animated, completion: nil)
    }

    func goToPreviousPage(animated: Bool = true) {
        guard let currentViewController = self.viewControllers?.first else { return }
        guard let previousViewController = dataSource?.pageViewController(self, viewControllerBefore: currentViewController) else { return }
        setViewControllers([previousViewController], direction: .reverse, animated: animated, completion: nil)
    }

}
Gligor
  • 2,862
  • 2
  • 33
  • 40
  • 1
    Much appreciated! – William T. Mallard May 16 '18 at 23:53
  • Happy it helped @WilliamT.Mallard – Gligor May 17 '18 at 01:43
  • 6
    If you're updating the dots in a delegate, you might want to use this code for the completion (instead of nil): `{ completed in self.delegate?.pageViewController?(self, didFinishAnimating: true, previousViewControllers: [], transitionCompleted: completed) }` – Andrew Duncan Aug 15 '18 at 19:07
  • @Recusiwe check the comment just above yours – Gligor Nov 06 '18 at 02:59
  • Hi, thanks for writing this, and this may be a stupid question, but once I create an extension of UIPageViewController, shouldn't I be able to call it on a class PageController: UIPageViewController? (PageController is the name). If I try to do this: PageController.goToNextPage(animated: true), I get: Instance member 'goToNextPage' cannot be used on type 'PageController'; did you mean to use a value of this type instead? – CristianMoisei Mar 16 '19 at 14:26
  • Hi @CristianMoisei, you absolutely can use the extension on classes that have `UIPageViewController` as superclass. I would check if your functions are in the same project, or if not if they are public perhaps? – Gligor Mar 17 '19 at 18:49
  • 1
    @CristianMoisei I had the same issue, but what I did is formatting correctly the closure. If it's let on a single line it wont work, it must be correctly formatted: `completed in` and then on a new line `self.delegate?.pageViewController...` – josher932 Nov 29 '22 at 00:17
9

Use this funtion and set the transition style for the animation you want.

enter image description here

enter image description here

JordanC
  • 1,303
  • 12
  • 28
  • 1
    JordanC> I need to turn the page on click of a button - so how do i use the above function? – Jasper May 28 '15 at 04:32
  • 2
    @Jasper Make a UIButton and when it's clicked, you figure out which page is next in your UIPageViewController and pass that page into the setViewControllers method. – JordanC May 28 '15 at 15:53
  • I don't know why, but I got the right page but if I call this method then nothing changes. – Lennart P. Apr 05 '18 at 12:52
9

Here is the swift 4 implementation with the delegates as well.

Since I also use the UIPageViewControllerDelegate, and the delegate methods weren't called with most of the setViewController solutions.

Thanks to Andrew Duncan's for his comment about the delegate.

// Functions for clicking next and previous in the navbar, Updated for swift 4
@objc func toNextArticle(){
    guard let currentViewController = self.viewControllers?.first else { return }

    guard let nextViewController = dataSource?.pageViewController( self, viewControllerAfter: currentViewController ) else { return }

    // Has to be set like this, since else the delgates for the buttons won't work
    setViewControllers([nextViewController], direction: .forward, animated: true, completion: { completed in self.delegate?.pageViewController?(self, didFinishAnimating: true, previousViewControllers: [], transitionCompleted: completed) })
}

@objc func toPreviousArticle(){
    guard let currentViewController = self.viewControllers?.first else { return }

    guard let previousViewController = dataSource?.pageViewController( self, viewControllerBefore: currentViewController ) else { return }

    // Has to be set like this, since else the delgates for the buttons won't work
    setViewControllers([previousViewController], direction: .reverse, animated: true, completion:{ completed in self.delegate?.pageViewController?(self, didFinishAnimating: true, previousViewControllers: [], transitionCompleted: completed) })
}
Vasco
  • 837
  • 8
  • 9
6

Here is a swift implementation of this:

private func slideToPage(index: Int, completion: (() -> Void)?) {
    let count = //Function to get number of viewControllers
    if index < count {
        if index > currentPageIndex {
            if let vc = viewControllerAtIndex(index) {
                self.pageViewController.setViewControllers([vc], direction: UIPageViewControllerNavigationDirection.Forward, animated: true, completion: { (complete) -> Void in
                    self.currentPageIndex = index
                    completion?()
                })
            }
        } else if index < currentPageIndex {
            if let vc = viewControllerAtIndex(index) {
                self.pageViewController.setViewControllers([vc], direction: UIPageViewControllerNavigationDirection.Reverse, animated: true, completion: { (complete) -> Void in
                    self.currentPageIndex = index
                    completion?()
                })
            }
        }
    }
}

viewControllerAtIndex(index: Int) -> UIViewController? is my own function to get the correct view controller to swipe to.

Swinny89
  • 7,273
  • 3
  • 32
  • 52
2

Just leaving this out here incase any one wants to use a defined protocol to navigate around and change view controllers programatically.

@objc protocol AppWalkThroughDelegate {
  @objc optional func goNextPage(forwardTo position: Int)
  @objc optional func goPreviousPage(fowardTo position: Int)
}

Use the above protocol and confirm to root UIPageViewController to manage navigation between view controllers.

Example below:

class AppWalkThroughViewController: UIPageViewController, UIPageViewControllerDataSource, AppWalkThroughDelegate {

    // Add list of view controllers you want to load

    var viewControllerList : [UIViewControllers] = {
       let firstViewController = FirstViewController()
       let secondViewController = SecondViewController() 
       // Assign root view controller as first responder
       secondViewController.delegate = self  

       let thirdViewController = ThirdViewController() 
       
    }

    override func viewDidLoad() {
       super.viewDidLoad()
    }
    
    // Navigate to next page 
    func goNextPage(fowardTo position: Int) {
       let viewController = self.viewControllerList[position]
       setViewControllers([viewController], direction: 
       UIPageViewControllerNavigationDirection.forward, animated: true, completion: nil)
    }

    }

Once achieved all that, child view controllers that need to make UIPageViewController move to next or previous page can use AppWalkThroughDelegate methods by passing a specific number onto delegate property.

Example below: Delegate method invoked once button pressed

    class SecondViewController: UIViewController {
       
        var delegate: AppWalkThroughDelegate!
        
        override func viewDidLoad() {
           super.viewDidLoad()
        }

         @IBAction func goNextPage(_ sender: Any) {
            // Can be any number but not outside viewControllerList bounds 
            delegate.goNextPage!(fowardTo: 2)
         }
    }
Sharkes Monken
  • 661
  • 5
  • 23