UIPageViewController: returns the current visible view

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

I overridden the viewDidAppear method for my child views to send the identifier to the parent view in my viewDidAppear method.

However, the problem is this: I cannot reliably use this id as id for the displayed page. because if the user rotates the page, but halfway decides to stop the rotation and return the page, viewDidAppear already called. (view is visible behind the curled page).

Maybe I should switch to a new identifier if the current view disappears. But I wonder if there is an easier way to bring back the currently visible view?

+85
ios iphone uipageviewcontroller
06 Dec 2018-11-12T00:
source share
20 answers

You must manually track the current page. The delegate method pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: tells you when to update this variable. The final argument to the transitionCompleted: method can tell you whether the user completed the transition to the page or not.

+95
Dec 06 2018-11-11T00:
source share

As in iOS 6, I found that the viewControllers property of the viewControllers is constantly updated, so it will always contain one view controller that represents the current page, and nothing more. That way, you can access the current page by calling viewControllers[0] ( viewControllers[0] you only show one view controller at a time).

The viewController array is updated only after the page is "locked" in place, so if the user decides to partially open the next page, it will not become the "current" page if they do not complete the transition.

If you want to track "page numbers," assign your view controllers an index value when they are created through the UIPageViewController data source 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 pay attention to the comments that this is unreliable.

+54
Apr 11 '13 at 18:29
source share

Unfortunately, all of the above methods did not help me. However, I found a solution using tags. This may not be the best, but it works and hopes that this will help 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 @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

+43
Mar 12 '14 at 16:45
source share

Creating Ole's answer ...

Here's how I applied 4 methods to track the current page and update the page indicator to the desired 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; } 
+34
Mar 28 '13 at 17:05
source share

The solution below worked for me.

Apple can avoid a lot of hassle by making its own scroll configuration based on the UIPageViewController more customizable. I only had to resort to overlaying the new UIView and UIPageControl because the native page numbering of the UIPageViewController does not support transparent background or repositioning in 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; } 
+31
Jun 22 '13 at 18:54
source share

Swift 4

No extra code. 3 ways to do it. Using the UIPageViewControllerDelegate Method .

 func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { guard completed else { return } // using content viewcontroller index guard let index = (pageViewController.viewControllers?.first as? ContentViewController)?.index else { return } // using viewcontroller 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 } } 
+15
Oct 28 '16 at 10:40
source share

I track the page index with a small function and pointing pageIndex as a 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 a change in orientation.

+11
Feb 09 '12 at 12:31
source share

I first used the Corey solution, but it did not work on iOS5, and then used

 - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{ if(completed) { _currentViewController = [pageViewController.viewControllers lastObject]; } } 

He tried to switch to different pages, and now it works well.

+5
Apr 05 '13 at
source share

Unfortunately, nothing works for me.

I have two view controllers, and when I scroll back the last view a little (about 20 pixels), it calls the delegate:

pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:

and stating that the current page (index) is not 0 .

Using a delegate inside a child of a viewController:

 - (void)ViewController:(id)VC didShowWithIndex:(long)page; // and a property @property (nonatomic) NSInteger index; 

which runs inside viewDidAppear , for example:

 - (void)viewDidAppear:(BOOL)animated { ... [self.delegate ViewController:self didShowWithIndex:self.index]; } 

Worked for me.

+2
Nov 20 '15 at 3:44
source share

It works reliably for me

I have a custom UIPageController. ThisController.currentPage page is updated from the displayed UIViewController in 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 } 
+2
Feb 08 '16 at 3:38
source share

Thanks for the answer, guys, I encountered a similar problem, I had to store the index. I modify my code a bit, insert 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]; } 
+1
Jan 13 '17 at 12:17
source share
 - (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 
0
Apr 01 '14 at 11:36 on
source share

I used view.tag for a while, trying to track the current page is too complicated.

In this code, the index is stored in the tag property of each view and is used to retrieve the next or previous VC. Using this method, you can also create an endless scroll. Check out the comment in the code to view this solution:

 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 } } 
0
Dec 26 '14 at 18:45
source share

Below is a demo code (in Swift 2) that demonstrates how to do this by introducing a simple tutorial on image fishing. 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 } } 
0
Feb 26 '16 at 10:31
source share

I have an array of viewControllers that I show in a 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 } } 

It is important to note that the setViewControllers UIPageViewController method does not give any delegate callback. Delegate callbacks represent only user touch actions in the UIPageViewController.

0
Feb 16 '17 at 6:48
source share

This is the solution I came across:

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

Using:

  let pageViewController = UIPageViewController() let delegate = DefaultUIPageViewControllerDelegate() delegate.didTransitionToViewControllerCallback = { pageViewController.title = $0.title } pageViewController.title = viewControllers.first?.title pageViewController.delegate = delegate 

Be sure to set the initial name

0
Jul 27 '17 at 9:07 on
source share

The easiest way to approach this IMHO is to use PageControl to store the potential result of the transition, and then return if the transition was canceled. This means that the page control changes as soon as the user starts scrolling, which is OK. This requires that you have your own UIViewControllers array (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 } } 
0
Sep 19 '17 at 4:10
source share

Tracking the current page number in delegate methods:

 func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController 

We use helper getVC to update our page number and return the next / prev vc. This is a good place to do this because getVC is only called if there is a next or previous vc.

 ///Helper func getVC(for pgNum: Int) -> BasePageVC { let vc = storyboard?.instantiateViewController(withIdentifier: "page"+String(pgNum)) as! BasePageVC vc.pageNumber = pgNum self.currentPage = pgNum vc.data = self.data return vc } /// Next Page func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { guard let vc = viewController as? BasePageVC else { fatalError("Vc is not BasePageViewController in viewControllerAfter") } let nextPage = vc.pageNumber! + 1 guard nextPage < self.data.pages.count - 1 else { return nil } return getVC(for: nextPage) } /// Previous Page func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let vc = viewController as? BasePageVC else { fatalError("Vc is not BasePageViewController in viewControllerAfter") } let prevPage = vc.pageNumber! - 1 guard prevPage >= 0 else { return nil } return getVC(for: prevPage) } 
0
Sep 07 '18 at 14:26
source share

How about a viewController request directly from a 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 only need a pageViewController.viewControllers?.first controller at a specific point in time, just use pageViewController.viewControllers?.first at that time.

0
Dec 07 '18 at 9:34
source share
 UIViewController *viewController = [pageViewController.viewControllers objectAtIndex:0]; NSUInteger currentIndex = [(ViewController*) viewController indexNumber]; 

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

-one
Jun 30 '15 at 8:49
source share



All Articles