17

These two methods viewControllerBeforeViewController and viewControllerAfterViewController of the UIPageViewControllerDataSource don't tell the direction of the swipe.

The method transitionCompleted of the UIPageViewController delegate doesn't help us much either. It just tells if the page was fully swiped.

So what method should I use to detect exactly the user direction (left or right)?

Probably these two properties may help:

let directionForward = UIPageViewControllerNavigationDirection.Forward
let directionReverse = UIPageViewControllerNavigationDirection.Reverse

My code looks like this:

import UIKit

class ProView: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

var pageViewController: UIPageViewController?

let characterImages = ["character1", "character2", "character1", "character2", "character1", "character2", "character1", "character2"]

override func viewDidLoad() {
    super.viewDidLoad()
    createPageViewController()
    setupPageControl()

    character = 1
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

// Forward, check if this IS NOT the last controller
func pageViewController(pageViewController: UIPageViewController,
    viewControllerAfterViewController ProView: UIViewController) -> UIViewController? {

        let itemController = ProView as PageItemController

        // Check if there is another view
        if itemController.itemIndex+1 < characterImages.count {

            return getItemController(itemController.itemIndex+1)
        }

        return nil
}


// Check if this IS NOT the first controller
func pageViewController(pageViewController: UIPageViewController,
    viewControllerBeforeViewController ProView: UIViewController) -> UIViewController? {

    let itemController = ProView as PageItemController

        if itemController.itemIndex < 0 {

            return getItemController(itemController.itemIndex-1)
        }

        return nil
}

private func getItemController(itemIndex: Int) -> PageItemController? {

    if itemIndex < characterImages.count {
        let pageItemController = self.storyboard!.instantiateViewControllerWithIdentifier("ItemController") as PageItemController
        pageItemController.itemIndex = itemIndex
        pageItemController.imageName = characterImages[itemIndex]
        return pageItemController
}

    return nil
}

func createPageViewController() {

    let pageController = self.storyboard!.instantiateViewControllerWithIdentifier("PageController") as UIPageViewController
    pageController.dataSource = self
    pageController.delegate = self

    if characterImages.count > 0 {
        let firstController = getItemController(0)!
        let startingViewControllers: NSArray = [firstController]
        pageController.setViewControllers(startingViewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
    }

    pageViewController = pageController
    addChildViewController(pageViewController!)
    self.view.addSubview(pageViewController!.view)
    pageViewController?.didMoveToParentViewController(self)
}

func setupPageControl() {
    let appearance = UIPageControl.appearance()
    appearance.pageIndicatorTintColor = UIColor.grayColor()
    appearance.currentPageIndicatorTintColor = UIColor.whiteColor()
}

func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
    return characterImages.count
}

func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {


}

// BETA

func pageViewController(PageItemController: UIPageViewController,
    didFinishAnimating finished: Bool,
    previousViewControllers pageViewController: [AnyObject],
    transitionCompleted completed: Bool)
{

    if (!completed)
    {
        // You do nothing because whatever page you thought
        // the book was on before the gesture started is still the correct page
        return;
    }

    // This is where you would know the page number changed and handle it appropriately

    character = workaround

    }


}

class PageItemController: UIViewController {

@IBOutlet weak var imageCharacterChoose: UIImageView!

var itemIndex: Int = 0
var imageName: String = "" {
    didSet {

        if let imageView = imageCharacterChoose {imageCharacterChoose.image = UIImage(named: imageName)
        }

    }
}
}

According to what pbasdf said,

var currentIndex: Int = 0
var nextIndex: Int = 0

func pageViewController(PageItemController: UIPageViewController,
    willTransitionToViewControllers pageViewController: [AnyObject]) {

        //                          pageViewController is the pending View Controller
        nextIndex = currentIndex

        // pageViewController...... how do I get its index?
}
Cesare
  • 9,139
  • 16
  • 78
  • 130

6 Answers6

30

You should use both the willTransitionToViewControllers: and the didFinishAnimating: delegate methods to work out whether the transition is forward or backward. Declare a couple of index variables at the start of your class, say currentIndex and nextIndex (both Int).

In the willTransitionToViewControllers method, set the nextIndex equal to the itemIndex of the pending view controller:

EDIT

func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [AnyObject]) {
    // the page view controller is about to transition to a new page, so take note
    // of the index of the page it will display.  (We can't update our currentIndex
    // yet, because the transition might not be completed - we will check in didFinishAnimating:)
    if let itemController = pendingViewControllers[0] as? PageItemController {
        nextIndex = itemController.itemIndex
    }
}

END EDIT

You can work out at this point whether the transition will be forward or backward: if nextIndex > currentIndex, then forward; if nextIndex < currentIndex then backward. Then in didFinishAnimating, if completed is true (so it completed the transition to the next view controller), set currentIndex equal to nextIndex so you can use currentIndex wherever you need to indicate which page is currently on screen:

EDIT

func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) {
    if (completed) {
        // the page view controller has successfully transitioned to the next view
        // controller, so we can update our currentIndex to the value we obtained 
        // in the willTransitionToViewControllers method:
        currentIndex = nextIndex

    }
}

Note that the first argument to these methods is the pageViewController (instance of UIPageViewController), not pageItemController which you have in your current code.

Finally, just to note: that enum you refer to (UIPageViewControllerNavigationDirection) is used only in the setViewControllers(_, direction:) method.

END EDIT

pbasdf
  • 21,386
  • 4
  • 43
  • 75
  • Thank you very much for answering precisely my question. May you please explain how do I get the index of the pending viewController. You say "...equal to the itemIndex of the pending view controller", but I have on clue about how to do that. The rest of the answer is clear and nicely put - Thanks again! – Cesare Jan 14 '15 at 06:24
  • @CeceXX I have updated my answer with some sample code. – pbasdf Jan 14 '15 at 17:26
  • I can't just THANK YOU enough! – Cesare Jan 14 '15 at 17:35
  • 1
    I'd like to add here. There is a case when you move to next controller and immediately swipe back in opposite direction. In that case "willTransitionToViewControllers" won't be called on last swipe. So you have to add: if (completed) { ...} else { nextIndex = previousViewController.index } – protspace Aug 06 '20 at 12:37
4

For my case, I was able to determine the swiping direction by using func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) only.

Here is how. Assume that you already have a way to reference the current index named currentIndex. In the above method, you already have the information about the previousViewControllers so that you can get the previous index named previousIndex.

Basically, here is the implementation inside the delegate method:

func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [AnyObject], transitionCompleted completed: Bool) {
  if completed {
    if previousIndex < currentIndex {   // forward

    }
    else if previousIndex > currentIndex {  // reverse

    }
  }
}
Lucas Huang
  • 3,998
  • 3
  • 20
  • 29
1

Assuming you have a local array of the ViewControllers, you can use this delegate method:

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    guard let current = self.viewControllers?.first else {
        // TODO handle error
        return
    }

    guard let index = self.pages.firstIndex(of: current) else {
        // TODO handle error
        return
    }

    self.currentIndex = index
}

The guard statements should not fail, but just in case....

gutte
  • 1,373
  • 2
  • 19
  • 31
1

Firstly in your viewControllerForIndex method assign viewcontroller.view.tag = index so that every viewcontroller.view has an assigned value

Have a currentIndex property which is initially assigned to that index of the viewcontroller which you have instantiated inside pageviewcontroller

Now in didFinishAnimating do the following

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if !completed {
            return     // return if animation is not completed
        }
       let pCont = bottomDataContainer?.viewControllers?[0] as? CustomVC   //take 
       out current VC

        if currentIndex < pCont?.view.tag ?? 0 {
            topNavView.goToNextDate()
        }else{
            topNavView.goToPrevDate()
        }
        selectedIndex = pCont?.view.tag ?? 0
    }
Chirag Shah
  • 3,034
  • 1
  • 30
  • 61
Kartik 123
  • 1,032
  • 1
  • 7
  • 5
1

You can achieve this by implementing the UIScrollViewDelegate:

var lastScrollDirection: UIPageViewController.NavigationDirection?

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
    
  self.lastScrollDirection = translation.x < 0 ? .forward : .reverse
}

Assigning the delegate can be trickier. Here is what I used on a project:

func assignScrollDelegate(rootView: UIView) {
  for view in rootView.subviews {
    if let scrollView = view as? UIScrollView {
      scrollView.delegate = self
    }
    else {
      assignScrollDelegate(rootView: view)
    }
  }
}

Call this when you have a reference to the UIPageViewController. For example, when your controller loads:

override func viewDidLoad() {
  self.assignScrollDelegate(rootView: self.pageController.view)
}
Blago
  • 4,697
  • 2
  • 34
  • 29
  • this is helpful but you may want to using `scrollViewDidEndDragging` rather than `scrollViewWillBeginDragging` to update your model – aaronium112 Sep 26 '21 at 11:32
0

There is no way to determine whether the user swiped forward or backward.

You need to use these two methods to provide the views before and after:

pageViewController:viewControllerBeforeViewController:

pageViewController:viewControllerAfterViewController:

You can keep track of an index by adding a property to the viewControllers and then you can compare the pageIndexes.

Chris Truman
  • 913
  • 1
  • 7
  • 25
  • How would I exactly do that? "You can keep track of an index by adding a property to the viewControllers and then you can compare the pageIndexes.". Thanks! – Cesare Jan 14 '15 at 06:30
  • @CeceXX The view controller would have a property called pageIndex like this tutorial: http://www.makemegeek.com/uipageviewcontroller-example-ios/ – Chris Truman Jan 14 '15 at 19:11