Est-il possible de tourner la page par programme dans UIPageViewController
?
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)
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!
}
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];
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)
}
}
}
}
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];
}
}
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.
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
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
}
}
}
- (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 :)
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"
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.
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;}
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];
}
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];
}
}
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.
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!