UIPageViewController: return the current visible view

匿名 (未验证) 提交于 2019-12-03 02:11:02

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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



回答4:

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


回答5:

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


回答6:

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.



回答7:

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


回答8:

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.



回答9:

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.



回答10:

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       } 


回答11:

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  *)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]; } 


回答12:

- (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 


回答13:

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  UIViewController? {         guard (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     }  } 


回答14:

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 = endIndex { return nil }       // Instead, We can normalize the index to be cyclical to create infinite scrolling       // if index  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   } } 


回答15:

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.



回答16:

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



回答17:

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


回答18:

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



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!