94

How do you know what is the current page/view displayed inside an UIPageViewController?

I have overridden the viewDidAppear method of my child views, so that they send an id to the parent view in their viewDidAppear method.

However, the problem is this: i cannot reliably use that id as id for the displayed page. because if the user turns the page but halfway through decides to stop the turning and put the page back, viewDidAppear will already have been called. (the view is visible behind the curled page).

Maybe i should only switch to a new id if the current view disappears. But I wonder if there is not a more simple way to return the view that is currently visible?

atxe
  • 5,029
  • 2
  • 36
  • 50
Jan
  • 1,582
  • 1
  • 13
  • 19
  • Have you tried using `viewDidAppear:animated:` instead? – Till Dec 06 '11 at 13:57
  • O, yes. I did use that. I edited the question to correct my mistake. – Jan Dec 06 '11 at 14:04
  • The way you edited the question makes no sense to me. Either you send that id from within viewWillAppear or from viewDidAppear. Please recheck your edit. – Till Dec 06 '11 at 14:21
  • Sorry, I did a bad job editing the question. I used viewDidAppear all along. I hope my last edit clarifies that. – Jan Dec 06 '11 at 14:31
  • Take a look at here guys, this works for me http://stackoverflow.com/a/36860663/4286947 – theDC Apr 27 '16 at 07:18
  • https://spin.atomicobject.com/2015/12/23/swift-uipageviewcontroller-tutorial/ this one works great – mbdavis Feb 07 '17 at 10:17

20 Answers20

112

You should manually keep track of the current page.

The delegate method pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: will tell you when to update that variable. The last argument of the method transitionCompleted: can tell you whether a user completed a page turn transition or not.

Then, you can get the currently presented View Controller by doing

self.viewControllers?.first
אורי orihpt
  • 2,358
  • 2
  • 16
  • 41
Ole Begemann
  • 135,006
  • 31
  • 278
  • 256
  • 1
    +1 for correct answer - I wish I had the chance of working with UIPageViewController and support iOS5 only. – Till Dec 06 '11 at 14:34
  • Thanks, that is exactly the clean solution I was looking for. better than delegates on the pages. – Jan Dec 06 '11 at 15:03
  • 3
    When you rotate the device, how do you keep track of current page? – Satyam Feb 03 '12 at 13:45
  • @till you can, just set your project's min version to ios5.0 – mtmurdock Mar 09 '12 at 04:08
  • @mtmurdock thanks for that suggestion - I may have been a bit unclear though. What I actually meant to say is that the projects I work on usually support older OS versions as well, hence I just can not rely on iOS 5 and above features only (at least not yet). – Till Mar 09 '12 at 08:42
  • @Till I see. The eternal struggle. – mtmurdock Mar 09 '12 at 17:37
  • For some reason, `didFinishAnimating:...` does not fire if page is flipped programmatically, using `setViewControllers:...` :-( Moar manual tracking. – Orc JMR Dec 19 '12 at 07:52
  • @Satyamsvv this comes a little late I know but for anyone interested you can keep track of the current page like described in this answer and on the delegate method `spineLocationForOrientation:` you set the `viewControllers` property to show that page whenever the device is rotated. – Fábio Oliveira May 17 '13 at 07:51
  • 84
    How can you tell whether to increment or decrement the current page number? – shim Jul 04 '14 at 05:34
  • See my answer below for full implementation in Swift 2, using this technique – HixField Feb 26 '16 at 10:38
  • @Ole this delegate is unreliable on iOS 7 as it not get called sometime but working fine others so, do u have any solution for iOS 7 supportability? – Dipika Nov 17 '16 at 05:33
59

As of iOS 6 I've found that the viewControllers property of UIPageViewController constantly updates so that it will always hold the one view controller that represents the current page, and nothing else. Thus, you can access the current page by calling viewControllers[0] (Assuming you only show one view controller at a time).

The viewController array only updates once the page "locks" into place, so if a user decides to partially reveal the next page it doesn't become the "current" page unless they complete the transition.

If you want to keep track of the "page numbers" assign your view controllers an index value as you create them through the UIPageViewController datasource methods.


So for example:

-(void)autoAdvance
    {
    UIViewController *currentVC = self.viewControllers[0];
    NSUInteger currentIndex = [myViewControllers indexOfObject:currentVC];

    if ( currentIndex >= (myViewControllers.count-1) ) return;

    [self setViewControllers:@[myViewControllers[ currentIndex+1 ]]
        direction:UIPageViewControllerNavigationDirectionForward
        animated:YES
        completion:nil];
    }
-(NSInteger)presentationIndexForPageViewController:
                         (UIPageViewController *)pageViewController
    {
    // return 0;
    UIViewController *currentVC = self.viewControllers[0];
    NSUInteger currentIndex = [myViewControllers indexOfObject:currentVC];
    return currentIndex;
    }

But note the comments that this is unreliable.

Fattie
  • 27,874
  • 70
  • 431
  • 719
Eddy Borja
  • 1,638
  • 17
  • 21
  • 2
    I found it to be unreliable. I couldn't find the source of the problem. When the animation finishes I access `viewControllers` property and I get the right view controllers but when I rotate, on the method `spineLocationForInterfaceOrientation`, I don't get the correct view controllers anymore. So I sticked to maintain my own currentPage property instead of relying always on the `viewControllers`. – Fábio Oliveira May 15 '13 at 10:30
  • Interesting, I'll play with it a bit to see how it behaves in these special cases – Eddy Borja May 16 '13 at 17:54
  • 2
    I also found it to be unreliable. It seems Apple just calls viewControllerBeforeViewController or viewControllerAfterViewController for each page turn, but doesn't update UIPageViewController.viewControllers. I don't know how they keep managing to get these types of things wrong, but they do. It really raises my workload, and breaks the clarity of my code (often violating D.R.Y. and other principles). – Zack Morris Jun 21 '13 at 00:29
  • 3
    @ZackMorris. It is absolutely incredible, you're quite right. It's an "Apple horror moment." Why oh why wouldn't they include the obvious functions like move one to the right, what page am I on currently, etc etc. It's quite incredible you can't "get the page you're on!!!" – Fattie Sep 25 '14 at 15:51
  • 1
    self.viewControllers[0] seems to work fine for me. But I just call setViewControllers once during initiation and left the rest to automation. (tested in iOS 8.4) – Bob Aug 28 '15 at 13:02
  • also viewControllers array can have two items if you have a double spread – Lee Probert Sep 26 '16 at 14:55
  • self.viewControllers[0] works fine on iOS 8 and 10 but sometimes unreliable on iOS 7. Do anyone have any solution or suggestion for iOS 7? – Dipika Nov 17 '16 at 05:37
45

Unfortunately, all above methods didn't help me. Nevertheless, I have found the solution by using tags. May be it's not the best, but it works and hope it helps someone:

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed 
{
    if (completed) {
        int currentIndex = ((UIViewController *)self.pageViewController.viewControllers.firstObject).view.tag;
        self.pageControl.currentPage = currentIndex;
    }
}

In Swift: (thanks to @Jessy)

func pageViewController(pageViewController: UIPageViewController,
    didFinishAnimating finished: Bool,
    previousViewControllers: [UIViewController],
    transitionCompleted completed: Bool)
{
    guard completed else { return }
    self.pageControl.currentPage = pageViewController.viewControllers!.first!.view.tag
}

Example: gist

Community
  • 1
  • 1
Victor
  • 914
  • 12
  • 15
  • 3
    This seems the most straightforward and worked well for me. I set the tags on adding the view controllers (for ex, HelpPage1ViewController *page1 = [self.storyboard instantiateViewControllerWithIdentifier:@"page1"];page1.view.tag=0;). Then you can set a currentPage property for the master viewcontroller to keep track of where you are. Be sure to add both protocols (""), not just the data source, and set the delegate to self ("self.pageViewController.delegate=self;") or the method won't be called. This baffled me for a bit. – James Toomey Jun 24 '14 at 14:19
  • 1
    @VictorM this solution does not work for me. I have a pageViewController with different images. It keeps returning tag 0 for each swipe, any idea why this happens? Thanks in advance – theDC Apr 26 '16 at 06:14
  • 1
    @VictorM i spended several days tried to save index, but accidently find this answer, i wish i could upvote 1000 times, thank you! – Evgeniy Kleban Jan 13 '17 at 12:14
  • 1
    Great solution Thanks spent hours trying to get around this – GyroCocoa Jun 21 '17 at 23:03
  • It only returns a value of 0. am I doing something wrong – N. Der Jan 28 '19 at 09:14
35

Building on Ole's Answer…

This is how I implemented the 4 methods to track the current page and update the page indicator to the correct index:

- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController{

    return (NSInteger)[self.model count];

}

- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController{

    return (NSInteger)self.currentIndex;
}

- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{

    SJJeanViewController* controller = [pendingViewControllers firstObject];
    self.nextIndex = [self indexOfViewController:controller];

}

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{

    if(completed){

        self.currentIndex = self.nextIndex;

    }

    self.nextIndex = 0;

}
Corey Floyd
  • 25,929
  • 31
  • 126
  • 154
  • 2
    I think the last line should be `self.nextIndex = self.currentIndex` rather than `self.nextIndex = 0` in order to properly restore the index when a transition is not completed. Otherwise you risk ending up with a `currentIndex` value of 0 when quickly paging back and forth somewhere in the middle of your pages. – hverlind Jul 25 '14 at 20:00
  • 1
    This doesn't fully work for me. Sometimes, pageViewController:willTransitionToViewControllers doesn't get called and hence nextIndex isn't changed, and the current index is then wrong. – Hayden Holligan Nov 26 '15 at 15:52
31

The solution below worked for me.

Apple could avoid a lot of hassle by making the native UIPageViewController scroll view pagination more configurable. I had to resort to overlaying a new UIView and UIPageControl just because the native UIPageViewController pagination won't support a transparent background or repositioning within the view frame.

- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
  if (!completed)
  {
    return;
  }
  NSUInteger currentIndex = [[self.pageViewController.viewControllers lastObject] index];
  self.pageControl.currentPage = currentIndex;
}
sirvine
  • 1,045
  • 11
  • 12
  • 1
    in this what you mean by index? – Yohan Feb 21 '14 at 12:41
  • I'm getting the index (i.e., the integer that represents the position of the current view controller in the array of self.pageViewController.viewControllers) and then using that to set the currentPage attribute of self.pageControl (which expects an integer value). Make sense? – sirvine Feb 22 '14 at 15:11
  • 6
    index is a custom property here I believe – Adam Johns Nov 25 '14 at 20:49
  • problem with this is that if you want to change page without animation method won't be called – Silviu St Aug 24 '15 at 19:02
19

Swift 4

No unnecessary code. 3 ways of doing it. Using UIPageViewControllerDelegate method.

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    guard completed else { return }

    // using content viewcontroller's index
    guard let index = (pageViewController.viewControllers?.first as? ContentViewController)?.index else { return }

    // using viewcontroller's view tag
    guard let index = pageViewController.viewControllers?.first?.view.tag else { return }

    // switch on viewcontroller
    guard let vc = pageViewController.viewControllers?.first else { return }
    let index: Int
    switch vc {
    case is FirstViewController:
        index = 0
    case is SecondViewController:
        index = 1
    default:
        index = 2
    }
}
Nikola Milicevic
  • 2,886
  • 3
  • 17
  • 17
11

I am keeping track of the page index by using a small function and specifying pageIndex as static NSInteger.

-(void) setPageIndex
{
    DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];

    pageIndex = [self.modelController indexOfViewController:theCurrentViewController];
}

and calling [self setPageIndex]; inside the function specified by Ole and also after detecting the change in orientation.

n.evermind
  • 11,944
  • 19
  • 78
  • 122
Wahib Ul Haq
  • 4,185
  • 3
  • 44
  • 41
5

I first used Corey's solution but it wasn't working on iOS5 then ended up using,

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{

    if(completed) {
        _currentViewController = [pageViewController.viewControllers lastObject];
    }
}

It tried switching through different pages and it works well for now.

Basit Ali
  • 619
  • 8
  • 10
3

Unfortunately nothing above works for me.

I have two view controllers and when I slightly (around 20px) scroll the last view backwards it triggers the delegate:

pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

and saying that the current page (index) is 0 which is wrong.

Using delegate inside child viewController something like:

- (void)ViewController:(id)VC didShowWithIndex:(long)page;

// and a property

@property (nonatomic) NSInteger index;

that is triggered inside viewDidAppear like:

- (void)viewDidAppear:(BOOL)animated
{
    ...

    [self.delegate ViewController:self didShowWithIndex:self.index];
}

Worked for me.

0yeoj
  • 4,500
  • 3
  • 23
  • 41
3

This works for me reliably

I have a custom UIPageController. This pageController.currentPage is updated from the displayed UIViewController in the viewWillAppear

   var delegate: PageViewControllerUpdateCurrentPageNumberDelegate?

      init(delegate: PageViewControllerUpdateCurrentPageNumberDelegate ){
        self.delegate = delegate
        super.init(nibName: nil, bundle: nil)
      }

      required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
      }

    override func viewWillAppear(animated: Bool) {

        if delegate != nil {
          self.delegate!.upateCurrentPageNumber(0) //(0) is the pageNumber corresponding to the displayed controller
        }
      } 

    //In the pageViewController 

    protocol PageViewControllerUpdateCurrentPageNumberDelegate {

      func upateCurrentPageNumber(currentPageIndex: Int)
    }

     create the view display controllers initializing with the delegate

    orderedViewControllers = {
              return [
                IntroductionFirstPageViewController(delegate: self),
                IntroductionSecondPageViewController(delegate: self),
                IntroductionThirdPageViewController(delegate: self)
              ]

            }()

    the function implementing the protocol

    func upateCurrentPageNumber(currentPageIndex: Int){
        pageControl.currentPage = currentPageIndex
      }
lguerra10
  • 249
  • 3
  • 5
1

I've been using view.tag for a while now, trying to keep track of the current page was too complicated.

In this code the index is stored within the tag property of each view and is used to fetch the next or previous VC. Using this method it's also possible to create an infinite scroll. Check out the comment in code to view this solution as well:

extension MyPageViewController: UIPageViewControllerDataSource {

  func viewControllerWithIndex(var index: Int) -> UIViewController! {
    let myViewController = storyboard?.instantiateViewControllerWithIdentifier("MyViewController") as MyViewController

    if let endIndex = records?.endIndex {
      if index < 0 || index >= endIndex { return nil }
      // Instead, We can normalize the index to be cyclical to create infinite scrolling
      // if index < 0 { index += endIndex }
      // index %= endIndex
    }

    myViewController.view.tag = index
    myViewController.record = records?[index]

    return myViewController
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    let index = viewController.view?.tag ?? 0
    return viewControllerWithIndex(index + 1)
  }

  func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
    let index = viewController.view?.tag ?? 0
    return viewControllerWithIndex(index - 1)
  }

  func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
    return records?.count ?? 0
  }

  func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
    return (pageViewController.viewControllers.first as? UIViewController)?.view.tag ?? 0
  }
}
Yariv Nissim
  • 13,273
  • 1
  • 38
  • 44
1

Thank for your answer guys, i faced similar problem, had to store index. I slightly modify my code, paste it below:

- (MenuListViewController *)viewControllerAtIndex:(NSInteger)index {

    if (_menues.count < 1)
        return nil;

    //    MenuListViewController *childViewController = [MenuListViewController initWithSecondSetFakeItems];
    MenuListViewController *childViewController = self.menues[index];
    childViewController.index = index;

    return childViewController;
}

#pragma mark - Page View Controller Data Source

- (void)pageViewController:(UIPageViewController *)pageViewController
        didFinishAnimating:(BOOL)finished
   previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers
       transitionCompleted:(BOOL)completed{

    if (completed) {

        NSUInteger currentIndex = ((MenuListViewController *)self.pageController.viewControllers.firstObject).index;
        NSLog(@"index %lu", (unsigned long)currentIndex);
    }
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSUInteger index = [(MenuListViewController *)viewController index];

    if (index == 0)
        return nil;

    index --;

    return [self viewControllerAtIndex:index];
}


- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{

    NSUInteger index = [(MenuListViewController *)viewController index];

    index ++;

    if (index == _menues.count)
        return nil;

    return [self viewControllerAtIndex:index];
}
Evgeniy Kleban
  • 6,794
  • 13
  • 54
  • 107
1

How about asking for a viewController directly from the UIPageViewController (Swift 4 version):

fileprivate weak var currentlyPresentedVC: UIViewController?

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    currentlyPresentedVC = pageViewController.viewControllers?.first
}

Or, if you just need the currently presented view controller at some point of time, simply use pageViewController.viewControllers?.first at that time.

Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
0
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {

    NSLog(@"Current Page = %@", pageViewController.viewControllers);

    UIViewController *currentView = [pageViewController.viewControllers objectAtIndex:0];

    if ([currentView isKindOfClass:[FirstPageViewController class]]) {
             NSLog(@"First View");
        }
        else if([currentView isKindOfClass:[SecondPageViewController class]]) {
             NSLog(@"Second View");
        }
        else if([currentView isKindOfClass:[ThirdViewController class]]) {
              NSLog(@"Third View");
        }
}

//pageViewController.viewControllers always return current visible View ViewController
Ruslan Soldatenko
  • 1,718
  • 17
  • 25
Avinash Kashyap
  • 861
  • 10
  • 16
0
UIViewController *viewController = [pageViewController.viewControllers objectAtIndex:0];
NSUInteger currentIndex = [(ViewController*) viewController indexNumber];

It will return current page index. and must use this code under the delegate function of UIPageViewController (didFinishAnimating).

Pang
  • 9,564
  • 146
  • 81
  • 122
Vinay Podili
  • 335
  • 1
  • 3
  • 16
0

Below demo code (in Swift 2) that demonstrates how this is done by implementing a simple image swiper tutorial. Comments in the code itself :

import UIKit

/*
VCTutorialImagePage represents one page show inside the UIPageViewController.
You should create this page in your interfacebuilder file:
- create a new view controller
- set its class to VCTutorialImagePage
- sets its storyboard identifier to "VCTutorialImagePage" (needed for the loadView function)
- put an imageView on it and set the contraints (I guess to top/bottom/left/right all to zero from the superview)
- connect it to the "imageView" outlet
*/

class VCTutorialImagePage : UIViewController {
    //image to display, configure this in interface builder
    @IBOutlet weak var imageView: UIImageView!
    //index of this page
    var pageIndex : Int = 0

    //loads a new view via the storyboard identifier
    static func loadView(pageIndex : Int, image : UIImage) -> VCTutorialImagePage {
        let storyboard = UIStoryboard(name: storyBoardHome, bundle: nil)
        let vc = storyboard.instantiateViewControllerWithIdentifier("VCTutorialImagePage") as! VCTutorialImagePage
        vc.imageView.image      = image
        vc.pageIndex            = pageIndex
        return vc
    }
}


/*
VCTutorialImageSwiper takes an array of images (= its model) and displays a UIPageViewController 
where each page is a VCTutorialImagePage that displays an image. It lets you swipe throught the 
images and will do a round-robbin : when you swipe past the last image it will jump back to the 
first one (and the other way arround).

In this process, it keeps track of the current displayed page index
*/

class VCTutorialImageSwiper: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    //our model = images we are showing
    let tutorialImages : [UIImage] = [UIImage(named: "image1")!, UIImage(named: "image2")!,UIImage(named: "image3")!,UIImage(named: "image4")!]
    //page currently being viewed
    private var currentPageIndex : Int = 0 {
        didSet {
            currentPageIndex=cap(currentPageIndex)
        }
    }
    //next page index, temp var for keeping track of the current page
    private var nextPageIndex : Int = 0


    //Mark: - life cylce


    override func viewDidLoad() {
        super.viewDidLoad()
        //setup page vc
        dataSource=self
        delegate=self
        setViewControllers([pageForindex(0)!], direction: .Forward, animated: false, completion: nil)
    }


    //Mark: - helper functions

    func cap(pageIndex : Int) -> Int{
        if pageIndex > (tutorialImages.count - 1) {
            return 0
        }
        if pageIndex < 0 {
            return (tutorialImages.count - 1)
        }
        return pageIndex
    }

    func carrouselJump() {
        currentPageIndex++
        setViewControllers([self.pageForindex(currentPageIndex)!], direction: .Forward, animated: true, completion: nil)
    }

    func pageForindex(pageIndex : Int) -> UIViewController? {
        guard (pageIndex < tutorialImages.count) && (pageIndex>=0) else { return nil }
        return VCTutorialImagePage.loadView(pageIndex, image: tutorialImages[pageIndex])
    }

    func indexForPage(vc : UIViewController) -> Int {
        guard let vc = vc as? VCTutorialImagePage else {
            preconditionFailure("VCPagImageSlidesTutorial page is not a VCTutorialImagePage")
        }
        return vc.pageIndex
    }


    //Mark: - UIPageView delegate/datasource


    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
        return pageForindex(cap(indexForPage(viewController)+1))
    }

    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        return pageForindex(cap(indexForPage(viewController)-1))
    }

    func pageViewController(pageViewController: UIPageViewController, willTransitionToViewControllers pendingViewControllers: [UIViewController]) {
        nextPageIndex = indexForPage(pendingViewControllers.first!)
    }

    func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if !finished { return }
        currentPageIndex = nextPageIndex
    }

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

    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
        return currentPageIndex
    }

}
HixField
  • 3,538
  • 1
  • 28
  • 54
0

I have a viewControllers array, that I display in the UIPageViewController.

extension MyViewController: UIPageViewControllerDataSource {

func presentationCount(for pageViewController: UIPageViewController) -> Int {
    return self.viewControllers.count
}

func presentationIndex(for pageViewController: UIPageViewController) -> Int {
    return self.currentPageIndex
}
}




extension MyViewController: UIPageViewControllerDelegate {

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

    if !completed { return }

    guard let viewController = previousViewControllers.last, let index = indexOf(viewController: viewController) else {
        return
    }

    self.currentPageIndex = index

}

fileprivate func indexOf(viewController: UIViewController) -> Int? {
    let index = self.viewControllers.index(of: viewController)
    return index
}
}

Important thing to note here is that the setViewControllers method of UIPageViewController does not give any delegate callback. The delegate callbacks only represent user touch actions in the UIPageViewController.

jarora
  • 5,384
  • 2
  • 34
  • 46
0

This is the solution I came up with:

class DefaultUIPageViewControllerDelegate: NSObject, UIPageViewControllerDelegate {

    // MARK: Public values
    var didTransitionToViewControllerCallback: ((UIViewController) -> Void)?

    // MARK: Private values
    private var viewControllerToTransitionTo: UIViewController!

    // MARK: Methods
    func pageViewController(
        _ pageViewController: UIPageViewController,
        willTransitionTo pendingViewControllers: [UIViewController]
    ) {
        viewControllerToTransitionTo = pendingViewControllers.last!
    }

    func pageViewController(
        _ pageViewController: UIPageViewController,
        didFinishAnimating finished: Bool,
        previousViewControllers: [UIViewController],
        transitionCompleted completed: Bool
    ) {
        didTransitionToViewControllerCallback?(viewControllerToTransitionTo)
    }
}

Usage:

 let pageViewController = UIPageViewController()
 let delegate = DefaultUIPageViewControllerDelegate()

 delegate.didTransitionToViewControllerCallback = {
    pageViewController.title = $0.title
 }

 pageViewController.title = viewControllers.first?.title
 pageViewController.delegate = delegate

Make sure to set the initial title

Peter Combee
  • 595
  • 4
  • 10
0

The simplest way to approach this IMHO is to use the PageControl to store the potential outcome of the transition and then revert if the transition was cancelled. This means that the page control changes as soon as the user starts swiping, which is ok by me. This requires that you have your own array of UIViewControllers (in this example called allViewControllers)

func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
    if let index = self.allViewControllers.index(of: pendingViewControllers[0]) {
        self.pageControl.currentPage = index
    }
}

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
    if !completed, let previousIndex = self.allViewControllers.index(of: previousViewControllers[0]) {
        self.pageControl.currentPage = previousIndex
    }
}
swift taylor
  • 610
  • 5
  • 10
0

In swift 5 and following sirvine answer

extension InnerDetailViewController: UIPageViewControllerDelegate {

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

        if completed {
            guard let newIndex = embeddedViewControllers.firstIndex(where: { $0 == pageViewController.viewControllers?.last }) else { return }
            print(newIndex)
            currentEmbeddedViewControllerIndex = newIndex
        }

    }

} 

In this case i don't care what class of UIViewController are embedded

Reimond Hill
  • 4,278
  • 40
  • 52