Is it possible to Turn page programmatically in UIPageViewController?

IosIos5UikitUipageviewcontroller

Ios Problem Overview


Is it possible to turn page programmatically in UIPageViewController?

Ios Solutions


Solution 1 - Ios

Yes it is possible with the method:

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

That is the same method used for setting up the first view controller on the page. Similar, you can use it to go to other pages.

Wonder why viewControllers is an array, and not a single view controller?

That's because a page view controller could have a "spine" (like in iBooks), displaying 2 pages of content at a time. If you display 1 page of content at a time, then just pass in a 1-element array.

An example in Swift:

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

Solution 2 - Ios

Since I needed this as well, I'll go into more detail on how to do this.

Note: I assume you used the standard template form for generating your UIPageViewController structure - which has both the modelViewController and dataViewController created when you invoke it. If you don't understand what I wrote - go back and create a new project that uses the UIPageViewController as it's basis. You'll understand then.

So, needing to flip to a particular page involves setting up the various pieces of the method listed above. For this exercise, I'm assuming that it's a landscape view with two views showing. Also, I implemented this as an IBAction so that it could be done from a button press or what not - it's just as easy to make it selector call and pass in the items needed.

So, for this example you need the two view controllers that will be displayed - and optionally, whether you're going forward in the book or backwards.

Note that I merely hard-coded where to go to pages 4 & 5 and use a forward slip. From here you can see that all you need to do is pass in the variables that will help you get these items...

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

}

Solution 3 - Ios

Here's another code example for a single paged view. Implementation for viewControllerAtIndex is omitted here, it should return the correct view controller for the given index.

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

Pages can be changed by calling

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

Solution 4 - Ios

I could totally be missing something here, but this solution seems a lot simpler than many others proposed.

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

Solution 5 - Ios

This code worked nicely for me, thanks.

This is what i did with it. Some methods for stepping forward or backwards and one for going directly to a particular page. Its for a 6 page document in portrait view. It will work ok if you paste it into the implementation of the RootController of the pageViewController template.

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

Solution 6 - Ios

Swift 4:

I needed to go to the next controller when i tap next on a pagecontroller view controller and also update the pageControl index, so this was the best and most straightforward solution for me:

let pageController = self.parent as! PageViewController

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

Solution 7 - Ios

What about using methods from dataSource?

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

Analogically for backward.

Solution 8 - Ios

However using this 'self.pageViewController setViewControllers' method call does not call 'viewDidDissapear' on the viewController for the page that has been turned away, whereas manually turning a page does call viewDidDissapear on the turned away page. I believe this is the cause of my crash

"The number of view controllers provided (0) doesn't match the number required (1) for the requested transition"

Solution 9 - Ios

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

Solution 10 - Ios

I also needed a button to navigate left on a PageViewController, one that should do exactly what you would expect from the swipe motion.

Since I had a few rules already in place inside "pageViewController:viewControllerBeforeViewController" for dealing with the natural swipe navigation, I wanted the button to re-use all that code, and simply could not afford to use specific indexes to reach pages as the previous answers did. So, I had to take an alternate solution.

Please note that the following code is for a Page View Controller that is a property inside my custom ViewController, and has its spine set to mid.

Here is the code I wrote for my Navigate Left button:

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

You can do something very similar to make a right navigation button from here.

Solution 11 - Ios

In case if PAGE CONTROL to indicate current page is O && You want pages to navigate via scroll & also by clicking custom buttons in Page ViewController, below may be helpful.

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

// Delegate called viewControllerAfterViewController when User Scroll to next page

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

}

//Custom button's (Prev & next) Logic

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

Solution 12 - Ios

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

}

//now call this method

[self moveToPage];

I hope it works :)

Solution 13 - Ios

For single page, I just edited the answer of @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];
}

Solution 14 - Ios

As @samwize pointed out, the way of paginating is by using

setViewControllers:array

Here's the way I did it: I have my datasource and the delegate separate. You can call this method and only change the "next" view. Previously you have to apply the paging rules. That is, you have to check direction and the next controller. If you use that method with your custom datasource the array of controllers that you're displaying won't change (since you'll use a custom array on a NSObject subclass).

Using your own datasource the method just sets a new view (with specific direction). Since you will still controll the pages that will display when normally swiping.

Solution 15 - Ios

I'm working on It since yesterday and I finally did It work. I couldn't use standard template totally, because I have three differents viewControllers as childviews:

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

I needed a trigger button only in the first one VC, so I added one property for pageViewController and model:

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

@interface VC1 : UIViewController

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

@end

I rewrote the model's init method to pass storyboard and pageViewController as parameters, so I could set those to my 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;
}

Finally, I added an IBAction in my vc1 to trigger the pageViewController method setViewControllers:direction:animated:completion::

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

Note I don't need to find VCs indexes, my button takes me to my second VC, but Its not hard get all of indexes, and keep track of your childviews:

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

I hope It helps you!

Solution 16 - Ios

Here is a solution using UIPageViewControllerDataSource methods:

The code keeps track of the current displayed view controller in the UIPageViewController.

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

First initialize currentViewController to the initial view controller set for the UIPageViewController.

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


Then keep track of currentViewController in the UIPageViewControllerDelegate methods: pageViewController: willTransitionToViewControllers: and 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;
    }
}

Finally, in the method of your choice (in the example below it's - (IBAction) onNext: (id) sender), you can programmatically switch to the next or previous controller using UIPageViewControllerDataSouce methods pageViewController:viewControllerBeforeViewController: , pageViewController:viewControllerAfterViewController: to get the next or prev view controller and navigate to it by calling [self.pageViewController setViewControllers: @[vc] direction: UIPageViewControllerNavigationDirectionForward animated: YES completion: 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];
    }
}

Solution 17 - Ios

As the ViewControllers are embedded in the PageViewController all you have to do is:

  • Retrieve parent view controller and cast as your relevant class.
  • Use that parents datasource property to get the next ViewController.
  • Use the SetParentViewControllers method to set and transition to the next page.

Example in Xamarin, but functionally similar to Swift etc. Following code would be in Button Click delegate in one of the ViewControllers being shown as a page:

var parent = ParentViewController as WelcomePageViewController;
var next = parent.DataSource.GetNextViewController(parent, this);

parent.SetViewControllers(new UIViewController[] { next }, UIPageViewControllerNavigationDirection.Forward, true, null);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRAGOpoRView Question on Stackoverflow
Solution 1 - IossamwizeView Answer on Stackoverflow
Solution 2 - IosJoeView Answer on Stackoverflow
Solution 3 - IoslekksiView Answer on Stackoverflow
Solution 4 - IoswfbarksdaleView Answer on Stackoverflow
Solution 5 - IosGrahamView Answer on Stackoverflow
Solution 6 - IosSam BingView Answer on Stackoverflow
Solution 7 - IosJakubKnejzlikView Answer on Stackoverflow
Solution 8 - IosnoRemaView Answer on Stackoverflow
Solution 9 - IosKing-WizardView Answer on Stackoverflow
Solution 10 - IostuliomirView Answer on Stackoverflow
Solution 11 - IosYogesh LolusareView Answer on Stackoverflow
Solution 12 - IosD.GarciaView Answer on Stackoverflow
Solution 13 - IosSmit ShahView Answer on Stackoverflow
Solution 14 - IoswolffanView Answer on Stackoverflow
Solution 15 - IosThomás PereiraView Answer on Stackoverflow
Solution 16 - IospconorView Answer on Stackoverflow
Solution 17 - IosGrant KView Answer on Stackoverflow