web-dev-qa-db-fra.com

UIPageViewController: renvoie la vue visible actuelle

Comment savez-vous quelle est la page/vue actuelle affichée à l'intérieur d'une UIPageViewController?

J'ai remplacé la méthode viewDidAppear de mes vues enfant afin qu'elles envoient un identifiant à la vue parent dans leur méthode viewDidAppear.

Cependant, le problème est le suivant: je ne peux pas utiliser cet identifiant de manière fiable comme identifiant pour la page affichée. parce que si l'utilisateur tourne la page mais qu'il est à mi-chemin décide de l'arrêter et de la remettre en arrière, viewDidAppear aura déjà été appelé. (la vue est visible derrière la page courbée).

Peut-être devrais-je passer à un nouvel identifiant si la vue actuelle disparaît. Mais je me demande s’il n’existe pas un moyen plus simple de retourner la vue actuellement visible? 

86
Jan

Vous devez suivre manuellement la page en cours. La méthode déléguée pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: vous indiquera quand mettre à jour cette variable. Le dernier argument de la méthode transitionCompleted: peut vous indiquer si un utilisateur a terminé ou non une transition de page.

98
Ole Begemann

Depuis iOS 6, la propriété viewControllers de UIPageViewController est constamment mise à jour, de sorte qu'elle contiendra toujours le seul contrôleur de vue représentant la page en cours, et rien d'autre. Ainsi, vous pouvez accéder à la page actuelle en appelant viewControllers[0] (en supposant que vous n’affichez qu’un seul contrôleur de vue à la fois).

Le tableau viewController n'est mis à jour que lorsque la page est "verrouillée". Ainsi, si un utilisateur décide de révéler partiellement la page suivante, il ne devient pas la page "actuelle" à moins de terminer la transition. 

Si vous souhaitez suivre les "numéros de page", attribuez à vos contrôleurs de vue une valeur d'index lors de leur création via les méthodes de source de données UIPageViewController.


Donc par exemple:

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

Mais notez les commentaires que cela n’est pas fiable.

56
Eddy Borja

Malheureusement, toutes les méthodes ci-dessus ne m'ont pas aidé. Néanmoins, j'ai trouvé la solution en utilisant des tags. Peut-être que ce n'est pas le meilleur, mais cela fonctionne et j'espère que cela aidera quelqu'un:

- (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: (merci à @ 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
}

Exemple:Gist

44
Victor M

S'appuyant sur la réponse d'Ole…

Voici comment j'ai implémenté les 4 méthodes pour suivre la page en cours et mettre à jour l'indicateur de page avec le bon 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
Corey Floyd

La solution ci-dessous a fonctionné pour moi. 

Apple pourrait éviter beaucoup de tracas en rendant la pagination de la vue de défilement native UIPageViewController plus configurable. J'ai dû recouvrir un nouveau UIView et UIPageControl simplement parce que la pagination UIPageViewController native ne prend pas en charge un arrière-plan transparent ou un repositionnement dans le cadre de la vue.

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

Swift 4

Pas de code inutile. 3 façons de le faire. Utiliser la méthode UIPageViewControllerDelegate .

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
    }
}
15
Nikola Milicevic

Je garde le suivi de l'index de page en utilisant une petite fonction et en spécifiant pageIndex comme NSInteger statique. 

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

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

et en appelant [self setPageIndex]; dans la fonction spécifiée par Ole et également après avoir détecté le changement d'orientation. 

11
Wahib Ul Haq

J'ai d'abord utilisé la solution de Corey mais cela ne fonctionnait pas sur iOS5, puis j'ai fini par utiliser,

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

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

Il a essayé de changer de page et cela fonctionne bien pour le moment.

5
Basit Ali

Cela fonctionne pour moi de manière fiable

J'ai un UIPageController personnalisé. Cette pageController.currentPage est mise à jour à partir de l'UIViewController affiché dans le 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
lguerra10

Malheureusement, rien ne marche plus pour moi.

J'ai deux contrôleurs de vue et quand je fais légèrement défiler la dernière vue vers l'arrière (environ 20 pixels), le délégué se déclenche:

pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: 

et en disant que la page actuelle (index) est 0 qui est faux.

Utiliser délégué dans viewController enfant quelque chose comme:

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

// and a property

@property (nonatomic) NSInteger index;

qui est déclenché dans viewDidAppear comme:

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

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

Travaillé pour moi.

2
0yeoj

Merci pour votre réponse les gars, j'ai fait face à un problème similaire, dû stocker index. Je modifie légèrement mon code, collez-le ci-dessous:

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

J'utilise view.tag depuis un moment maintenant, essayer de garder une trace de la page en cours était trop compliqué. 

Dans ce code, l'index est stocké dans la propriété tag de chaque view et est utilisé pour récupérer le VC suivant ou précédent. En utilisant cette méthode, il est également possible de créer un défilement infini. Consultez le commentaire dans le code pour afficher cette solution également:

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

Ci-dessous, le code de démonstration (dans Swift 2) qui montre comment cela est réalisé en mettant en œuvre un tutoriel simple sur les images swiper. Commentaires dans le code lui-même:

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
HixField

Garder une trace de notre numéro de page actuel dans les méthodes de délégué:

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

Nous utilisons un helper getVC pour mettre à jour notre numéro de page et renvoyer le prochain/précédent vc. C’est un bon endroit pour faire cela car getVC n’est appelé que s’il existe un vc précédent ou suivant.

///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
hidden-username

Voici la solution que j'ai trouvée:

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

Assurez-vous de définir le titre initial

0
Peter Combee

Pourquoi ne pas demander une viewController directement à partir de la UIPageViewController (version de Swift 4):

fileprivate weak var currentlyPresentedVC: UIViewController?

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

Ou, si vous avez simplement besoin du contrôleur de vue présenté à un moment donné, utilisez simplement pageViewController.viewControllers?.first à ce moment-là.

0
Milan Nosáľ
- (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
avinash

J'ai un tableau viewControllers que j'affiche dans 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
}
}

Il est important de noter ici que la méthode setViewControllers de UIPageViewController ne donne aucun rappel de délégué. Les callbacks des délégués représentent uniquement les actions tactiles de l'utilisateur dans UIPageViewController.

0
jarora

La méthode la plus simple pour aborder cet IMHO consiste à utiliser PageControl pour stocker le résultat potentiel de la transition, puis à revenir en arrière si la transition a été annulée. Cela signifie que le contrôle de page change dès que l'utilisateur commence à balayer, ce qui me convient. Cela nécessite que vous disposiez de votre propre tableau de UIViewControllers (appelé allViewControllers dans cet exemple).

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
swift taylor