web-dev-qa-db-fra.com

Est-il possible de tourner la page par programme dans UIPageViewController?

Est-il possible de tourner la page par programme dans UIPageViewController?

145
RAGOpoR

Oui c'est possible avec la méthode:

- (void)setViewControllers:(NSArray *)viewControllers 
                 direction:(UIPageViewControllerNavigationDirection)direction 
                  animated:(BOOL)animated 
                completion:(void (^)(BOOL finished))completion;`

Cette méthode est identique à celle utilisée pour configurer le premier contrôleur de vue sur la page. De même, vous pouvez l’utiliser pour accéder à d’autres pages.

Je me demande pourquoi viewControllers est un tableau et non un contrôleur de vue unique?

En effet, un contrôleur d'affichage de page pourrait avoir une "colonne" (comme dans iBooks), affichant 2 pages de contenu à la fois. Si vous affichez 1 page de contenu à la fois, passez simplement dans un tableau à 1 élément.

Un exemple dans Swift:

pageContainer.setViewControllers([displayThisViewController], direction: .Forward, animated: true, completion: nil)
213
samwize

Comme j'avais également besoin de cela, je vais entrer dans les détails pour savoir comment faire cela.

Remarque: je suppose que vous avez utilisé le formulaire de modèle standard pour générer votre structure UIPageViewController - dans laquelle les deux variables modelViewController et dataViewController ont été créées lors de son invocation. Si vous ne comprenez pas ce que j'ai écrit - retournez en arrière et créez un nouveau projet utilisant la variable UIPageViewController comme base. Vous comprendrez alors.

Donc, pour retourner à une page particulière, il faut configurer les différentes parties de la méthode ci-dessus. Pour cet exercice, je suppose qu’il s’agit d’une vue paysage avec deux vues. En outre, j’ai mis cela en œuvre en tant qu’IBAction pour pouvoir le faire d’une simple pression sur un bouton ou autre chose - c’est tout aussi facile de faire appel au sélecteur et de transmettre les éléments nécessaires.

Ainsi, pour cet exemple, vous avez besoin des deux contrôleurs de vue qui seront affichés - et éventuellement, que vous avanciez dans le livre ou en arrière.

Notez que je me suis contenté de coder en dur où aller aux pages 4 et 5 et d’utiliser un bordereau d’avance…. À partir de là, vous pouvez voir que tout ce que vous avez à faire est de passer les variables qui vous aideront à obtenir ces éléments….

-(IBAction) flipToPage:(id)sender {

    // Grab the viewControllers at position 4 & 5 - note, your model is responsible for providing these.  
    // Technically, you could have them pre-made and passed in as an array containing the two items...

    DataViewController *firstViewController = [self.modelController viewControllerAtIndex:4 storyboard:self.storyboard];
    DataViewController *secondViewController = [self.modelController viewControllerAtIndex:5 storyboard:self.storyboard];

    //  Set up the array that holds these guys...

    NSArray *viewControllers = nil;

    viewControllers = [NSArray arrayWithObjects:firstViewController, secondViewController, nil];

    //  Now, tell the pageViewContoller to accept these guys and do the forward turn of the page.
    //  Again, forward is subjective - you could go backward.  Animation is optional but it's 
    //  a Nice effect for your audience.

      [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];

    //  Voila' - c'est fin!  

}
35
Joe

Voici un autre exemple de code pour une seule vue paginée. L'implémentation de viewControllerAtIndex est omise ici, elle devrait renvoyer le contrôleur de vue correct pour l'index donné.

- (void)changePage:(UIPageViewControllerNavigationDirection)direction {
    NSUInteger pageIndex = ((FooViewController *) [_pageViewController.viewControllers objectAtIndex:0]).pageIndex;

    if (direction == UIPageViewControllerNavigationDirectionForward) {
        pageIndex++;
    }
    else {
        pageIndex--;
    }

    FooViewController *viewController = [self viewControllerAtIndex:pageIndex];

    if (viewController == nil) {
        return;
    }

    [_pageViewController setViewControllers:@[viewController]
                                  direction:direction
                                   animated:YES
                                 completion:nil];
}

Les pages peuvent être changées en appelant

[self changePage:UIPageViewControllerNavigationDirectionReverse];
[self changePage:UIPageViewControllerNavigationDirectionForward];
27
lekksi

Il se peut que je manque totalement quelque chose ici, mais cette solution semble beaucoup plus simple que beaucoup d’autres proposées.

extension UIPageViewController {
    func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
        if let currentViewController = viewControllers?[0] {
            if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) {
                setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
            }
        }
    }
}
14
wfbarksdale

Ce code a bien fonctionné pour moi, merci. 

C'est ce que j'ai fait avec ça. Quelques méthodes pour avancer ou reculer et une pour aller directement à une page particulière. Son pour un document de 6 pages en mode portrait. Cela fonctionnera bien si vous le collez dans l'implémentation du RootController du modèle pageViewController.

-(IBAction)pageGoto:(id)sender {

    //get page to go to
    NSUInteger pageToGoTo = 4;

    //get current index of current page
    DataViewController *theCurrentViewController = [self.pageViewController.viewControllers   objectAtIndex:0];
    NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];

    //get the page(s) to go to
    DataViewController *targetPageViewController = [self.modelController viewControllerAtIndex:(pageToGoTo - 1) storyboard:self.storyboard];

    DataViewController *secondPageViewController = [self.modelController viewControllerAtIndex:(pageToGoTo) storyboard:self.storyboard];

    //put it(or them if in landscape view) in an array
    NSArray *theViewControllers = nil;    
    theViewControllers = [NSArray arrayWithObjects:targetPageViewController, secondPageViewController, nil];


    //check which direction to animate page turn then turn page accordingly
    if (retreivedIndex < (pageToGoTo - 1) && retreivedIndex != (pageToGoTo - 1)){
        [self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
    }

    if (retreivedIndex > (pageToGoTo - 1) && retreivedIndex != (pageToGoTo - 1)){
        [self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL];
    }

}


-(IBAction)pageFoward:(id)sender {

    //get current index of current page
    DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
    NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];

    //check that current page isn't first page
    if (retreivedIndex < 5){

        //get the page to go to
        DataViewController *targetPageViewController = [self.modelController viewControllerAtIndex:(retreivedIndex + 1) storyboard:self.storyboard];

        //put it(or them if in landscape view) in an array
        NSArray *theViewControllers = nil;    
        theViewControllers = [NSArray arrayWithObjects:targetPageViewController, nil];

        //add page view
        [self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];

    }

}


-(IBAction)pageBack:(id)sender {


    //get current index of current page
    DataViewController *theCurrentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
    NSUInteger retreivedIndex = [self.modelController indexOfViewController:theCurrentViewController];

    //check that current page isn't first page
    if (retreivedIndex > 0){

    //get the page to go to
    DataViewController *targetPageViewController = [self.modelController viewControllerAtIndex:(retreivedIndex - 1) storyboard:self.storyboard];

    //put it(or them if in landscape view) in an array
    NSArray *theViewControllers = nil;    
    theViewControllers = [NSArray arrayWithObjects:targetPageViewController, nil];

    //add page view
    [self.pageViewController setViewControllers:theViewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:NULL];

    }

}
14
Graham

Qu'en est-il des méthodes de dataSource?

UIViewController *controller = [pageViewController.dataSource pageViewController:self.pageViewController viewControllerAfterViewController:pageViewController.viewControllers.firstObject];
[pageViewController setViewControllers:@[controller] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];

Analogique pour reculer.

5
JakubKnejzlik

Swift 4:

J'avais besoin d'aller au prochain contrôleur lorsque j'appuyais sur le suivant sur un contrôleur de vue pagecontroller et mettais également à jour l'index pageControl, donc c'était la meilleure solution et la plus simple pour moi:

let pageController = self.parent as! PageViewController

pageController.setViewControllers([parentVC.orderedViewControllers[1]], direction: .forward, animated: true, completion: nil)

pageController.pageControl.currentPage = 1
3
Sam Bing

Dans Swift :

import UIKit

extension HomeViewController {

    // MARK: - Carousel management.

    func startTimerForMovingCarousel(let numberOfSecondsBetweenFiringsOfTheTimer: NSTimeInterval) {
        if (self.timerForMovingCarousel == nil) {
            self.timerForMovingCarousel = NSTimer.scheduledTimerWithTimeInterval(numberOfSecondsBetweenFiringsOfTheTimer,
                                                                            target: self,
                                                                            selector: "moveCarousel",
                                                                            userInfo: nil,
                                                                            repeats: true)
        }
    }

    func moveCarousel() {
        println("I move the carousel.")

        let currentContentViewControllerDisplayed = self.pageViewController!.viewControllers[0] as! ContentViewController

        var index: Int = currentContentViewControllerDisplayed.index

        if index == self.arrayViewControllers.count - 1 {
            index = 0
        } else {
            ++index
        }

        let contentViewControllerToDisplay = self.arrayViewControllers[index] as! ContentViewController

        self.pageViewController.setViewControllers([contentViewControllerToDisplay],
                                                    direction: UIPageViewControllerNavigationDirection.Forward,
                                                    animated: true,
                                                    completion: nil)

        self.pageControl.currentPage = contentViewControllerToDisplay.index
    }

    func invalidateTimerForMovingCarousel() {
        if (self.timerForMovingCarousel != nil) {
            self.timerForMovingCarousel.invalidate()
            self.timerForMovingCarousel = nil
        }
    }

}
3
King-Wizard
- (void)moveToPage {

    NSUInteger pageIndex = ((ContentViewController *) [self.pageViewController.viewControllers objectAtIndex:0]).pageIndex;

    pageIndex++;

    ContentViewController *viewController = [self viewControllerAtIndex:pageIndex];

if (viewController == nil) {
        return;
    }
    [self.pageViewController setViewControllers:@[viewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];

}

// appelle maintenant cette méthode

[auto moveToPage];

J'espère que ca fonctionne :)

1
D.Garcia

Cependant, l'utilisation de cet appel à la méthode 'self.pageViewController setViewControllers' n'appelle pas 'viewDidDissapear' sur le viewController pour la page qui a été refoulée, alors que le fait de tourner manuellement appelle viewDidDissapear sur la page refermée. Je crois que c'est la cause de mon crash 

"Le nombre de contrôleurs de vue fournis (0) ne correspond pas au nombre requis (1) pour la transition demandée"

1
noRema

J'avais également besoin d'un bouton pour naviguer à gauche sur un PageViewController, un bouton qui devrait faire exactement ce que vous attendez du mouvement de balayage.

Comme j'avais quelques règles déjà en place dans "pageViewController: viewControllerBeforeViewController" pour gérer la navigation par balayage naturel, je voulais que le bouton réutilise tout ce code, et simplement ne pouvait pas se permettre d'utiliser des index pour accéder aux pages comme les réponses précédentes. Donc, je devais prendre une solution alternative.

Veuillez noter que le code suivant est destiné à un contrôleur de vue de page qui est une propriété de mon ViewController personnalisé et dont le dos est défini sur mid.

Voici le code que j'ai écrit pour mon bouton Naviguer à gauche:

- (IBAction)btnNavigateLeft_Click:(id)sender {

    // Calling the PageViewController to obtain the current left page
    UIViewController *currentLeftPage = [_pageController.viewControllers objectAtIndex:0];

    // Creating future pages to be displayed
    NSArray *newDisplayedPages = nil;
    UIViewController *newRightPage = nil;
    UIViewController *newLeftPage = nil;

    // Calling the delegate to obtain previous pages
    // My "pageViewController:viewControllerBeforeViewController" method returns nil if there is no such page (first page of the book).
    newRightPage = [self pageViewController:_pageController viewControllerBeforeViewController:currentLeftPage];
    if (newRightPage) {
        newLeftPage = [self pageViewController:_pageController viewControllerBeforeViewController:newRightPage];
    }

    if (newLeftPage) {
        newDisplayedPages = [[NSArray alloc] initWithObjects:newLeftPage, newRightPage, nil];
    }

    // If there are two new pages to display, show them with animation.
    if (newDisplayedPages) {
        [_pageController setViewControllers:newDisplayedPages direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
    }
}

Vous pouvez faire quelque chose de très similaire pour créer un bouton de navigation de droite à partir d’ici.

1
tuliomir

Dans le cas si le contrôle de page indique que la page en cours est O && Vous souhaitez que les pages naviguent avec le défilement et également en cliquant sur les boutons personnalisés de Page ViewController, les éléments ci-dessous peuvent être utiles.

//Set Delegate & Data Source for PageView controller [Say in View Did Load]
   self.pageViewController.dataSource = self;
   self.pageViewController.delegate = self;

// Delegates called viewControllerBeforeViewController when User Scroll to previous page 

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

    [_nextBtn setTitle:@"Next" forState:UIControlStateNormal];

    NSUInteger index = ((PageContentViewController*) viewController).pageIndex;

    if (index == NSNotFound){
        return nil;
    }else if (index > 0){
        index--;
    }else{
        return nil;
    }        
    return [self viewControllerAtIndex:index];        
}

// délégué appelé viewControllerAfterViewController lorsque l'utilisateur passe à la page suivante 

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

    NSUInteger index = ((PageContentViewController*) viewController).pageIndex;

if (index == NSNotFound){
    return nil;
}else if (index < 3){
    index++;
}else{
    return nil;
}
return [self viewControllerAtIndex:index];}

//Delegate called while transition of pages. The need to apply logic in this delegate is to maintain the index of current page.

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

buttonIndex = (int)((PageContentViewController*) pendingViewControllers.firstObject).pageIndex;

}

-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{    
if (buttonIndex == 0){
    _backButton.hidden = true;
}else if (buttonIndex == [self.pageImages count] - 1){
    _backButton.hidden = false;
    [_nextBtn setTitle:@"Begin" forState:UIControlStateNormal];
}else{
    _backButton.hidden = false;
}

}

// Logique du bouton personnalisé (Précédent et suivant)

-(void)backBtnClicked:(id)sender{
    if (buttonIndex > 0){
buttonIndex -= 1;
    }
    if (buttonIndex < 1) {
        _backButton.hidden = YES;
    }
    if (buttonIndex >=0) {
         [_nextBtn setTitle:@"Next" forState:UIControlStateNormal];
        PageContentViewController *startingViewController = [self viewControllerAtIndex:buttonIndex];
        NSArray *viewControllers = @[startingViewController];
        [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
    }

}

-(void)nextBtnClicked:(id)sender{
if (buttonIndex < 3){
buttonIndex += 1;
}
if(buttonIndex == _pageImages.count){
   //Navigate Outside Pageview controller        
}else{        
    if (buttonIndex ==3) {

        [_nextBtn setTitle:@"Begin" forState:UIControlStateNormal];
    }
    _backButton.hidden = NO;

    PageContentViewController *startingViewController = [self viewControllerAtIndex:buttonIndex];
    NSArray *viewControllers = @[startingViewController];
    [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}

}

//BUTTON INDEX & MAX COUNT
-(NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController{
    return [self.pageImages count];}

-(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController{
    return  buttonIndex;}
1
Yogesh Lolusare

Pour une seule page, je viens de modifier la réponse de @Jack Humphries

-(void)viewDidLoad
{
  counter = 0;
}
-(IBAction)buttonClick:(id)sender
{
  counter++;
  DataViewController *secondVC = [self.modelController viewControllerAtIndex:counter storyboard:self.storyboard];
  NSArray *viewControllers = nil;          
  viewControllers = [NSArray arrayWithObjects:secondVC, nil];          
  [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
}
1
Smit Shah

Voici une solution utilisant les méthodes UIPageViewControllerDataSource:

Le code conserve la trace du contrôleur de vue actuellement affiché dans le UIPageViewController.

...
@property (nonatomic, strong) UIViewController *currentViewController;
@property (nonatomic, strong) UIViewController *nextViewController;
...

Initialisez tout d'abord currentViewController sur le contrôleur de vue initial défini pour le UIPageViewController.

- (void) viewDidLoad {
    [super viewDidLoad];
    ...
    self.currentViewController = self.viewControllers[0];
    ...
    [self.pageViewController setViewControllers: @[self.currentViewController] direction: dir animated: YES completion: nil];
}


Gardez ensuite trace de currentViewController dans les méthodes UIPageViewControllerDelegate}: pageViewController: willTransitionToViewControllers: et pageViewController: didFinishAnimating: previousViewControllers: transitionCompleted:.


- (nullable UIViewController *) pageViewController: (UIPageViewController *) pageViewController viewControllerBeforeViewController: (UIViewController *) viewController {
    NSInteger idx = [self.viewControllers indexOfObject: viewController];
    if (idx > 0) {
        return self.viewControllers[idx - 1];
    } else {
        return nil;
    }
}

- (nullable UIViewController *) pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController: (UIViewController *) viewController {
    NSInteger idx = [self.viewControllers indexOfObject: viewController];
    if (idx == NSNotFound) {
        return nil;
    } else {
        if (idx + 1 < self.viewControllers.count) {
            return self.viewControllers[idx + 1];
        } else {
            return nil;
        }
    }
}

- (void) pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
    self.nextViewController = [pendingViewControllers firstObject];
}

- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed {
    if (completed) {
        self.currentViewController = self.nextViewController;
    }
}

Enfin, dans la méthode de votre choix (dans l'exemple ci-dessous, il s'agit de - (IBAction) onNext: (id) émetteur), vous pouvez passer par programme au contrôleur suivant ou précédent à l'aide de UIPageViewControllerDataSouce méthodes pageViewController: viewControllerBeforeViewController:, pageViewController: viewControllerAfterViewController: pour obtenir le contrôleur de vue précédent ou précédent et y accéder en appelant [self.pageViewController setViewControllers: @ [vc] : UIPageViewControllerNavigationDirectionForward animé: YES complétion: nil];


- (void) onNext {
    UIViewController *vc = [self pageViewController: self.tutorialViewController viewControllerAfterViewController: self.currentViewController];
    if (vc != nil) {
        self.currentViewController = vc;
        [self.tutorialViewController setViewControllers: @[vc] direction: UIPageViewControllerNavigationDirectionForward animated: YES completion: nil];
    }
}

0
pconor

Comme @samwize l’a souligné, la méthode de pagination consiste à utiliser

setViewControllers:array

Voici comment je l'ai fait: J'ai séparé ma source de données et le délégué. Vous pouvez appeler cette méthode et ne changer que la vue "suivante". Auparavant, vous devez appliquer les règles de pagination. C'est-à-dire que vous devez vérifier la direction et le prochain contrôleur. Si vous utilisez cette méthode avec votre source de données personnalisée, le tableau de contrôleurs que vous affichez ne changera pas (car vous utiliserez un tableau personnalisé sur une sous-classe NSObject).

En utilisant votre propre source de données, la méthode définit simplement une nouvelle vue (avec une direction spécifique). Puisque vous contrôlerez toujours les pages qui s'afficheront normalement.

0
wolffan

J'y travaille depuis hier et j'ai finalement réussi à le faire fonctionner. Je ne pouvais pas utiliser totalement le modèle standard, car j'ai trois viewControllers différents sous la forme d'enfants:

1 - rootViewController;
2 - model;
3 - viewControllers
3.1 - VC1;
3.2 - VC2;
3.3 - VC3.
Note: all of VCs has Storyboard ID.

J'avais besoin d'un bouton de déclenchement uniquement dans le premier VC, alors j'ai ajouté une propriété pour pageViewController et model:

#import <UIKit/UIKit.h>
#import "Model.h"

@interface VC1 : UIViewController

@property (nonatomic, strong) UIPageViewController *thePageViewController;
@property (nonatomic, strong) SeuTimePagesModel *theModel;

@end

J'ai réécrit la méthode init du modèle pour passer le storyboard et pageViewController en tant que paramètres, afin de pouvoir les définir sur mon VC1:

- (id)initWithStoryboard:(UIStoryboard *)storyboard
   andPageViewController:(UIPageViewController *)controller
{
    if (self = [super init])
    {
        VC1 *vc1 = [storyboard instantiateViewControllerWithIdentifier:@"VC1"];
        vc1.pageViewController = controller;
        vc1.model = self;

        VC2 *vc2 = [storyboard instantiateViewControllerWithIdentifier:@"VC2"];
        VC3 *vc3 = [storyboard instantiateViewControllerWithIdentifier:@"VC3"];
        self.pageData = [[NSArray alloc] initWithObjects:vc1, vc2, vc3, nil];
    }

    return self;
}

Enfin, j'ai ajouté une action IBAction dans mon vc1 pour déclencher la méthode pageViewControllersetViewControllers: direction: animée: complétion::

- (IBAction)go:(id)sender
{
    VC2 *vc2 = [_model.pages objectAtIndex:1];
    NSArray *viewControllers = @[vc2];
    [_pageViewController setViewControllers:viewControllers
                                  direction:UIPageViewControllerNavigationDirectionForward
                                   animated:YES
                                 completion:nil];
}

Remarque Je n'ai pas besoin de trouver d'index de VC, mon bouton m'amène à mon deuxième VC, mais ce n'est pas difficile d'obtenir tous les index et de suivre vos vues d'enfants:

- (IBAction)selecionaSeuTime:(id)sender
{
    NSUInteger index = [_model.pages indexOfObject:self];
    index++;

    VC2 *vc2 = [_model.pages objectAtIndex:1];
    NSArray *viewControllers = @[vc2];
    [_pageViewController setViewControllers:viewControllers
                                  direction:UIPageViewControllerNavigationDirectionForward
                                   animated:YES
                                 completion:nil];
}

J'espère que ça t'aide!

0
Tom Calmon