32

I m using UIPageViewController on iPad where I need to show a firstviewController in the first page and ContentViewController in the next page in landscape.

If I set the NSArray with two viewControllers the app is crashes at [self.pagviewController setViewController:] with the following exception:

The number of provided view controllers (2) doesn't match the number required (1) for the requested spine location (UIPageViewControllerSpineLocationMin)

Below is the code:

#pragma mark - UIPageViewControllerDataSource Methods

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController 
      viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)viewController textContents]];
    if(currentIndex == 0)
    {
        return nil;
    }
    ContentViewController *contentViewController = [[ContentViewController alloc] init];
    contentViewController.textContents = [self.modelArray objectAtIndex:currentIndex - 1];
    return contentViewController;
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
       viewControllerAfterViewController:(UIViewController *)viewController
{
    NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)viewController textContents]];
    if(currentIndex == self.modelArray.count-1)
    {
        return nil;
    }
    ContentViewController *contentViewController = [[ContentViewController alloc] init];
    contentViewController.textContents = [self.modelArray objectAtIndex:currentIndex + 1];

    return contentViewController;
}




//#pragma mark - UIPageViewControllerDelegate Methods

- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
                   spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
    if(UIInterfaceOrientationIsPortrait(orientation))
    {
        //Set the array with only 1 view controller
        UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
        NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];

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

        //Important- Set the doubleSided property to NO.
        self.pageViewController.doubleSided = NO;
        //Return the spine location
        return UIPageViewControllerSpineLocationMin;
    }
    else
    {
        NSArray *viewControllers = nil;
        ContentViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];

        NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)currentViewController textContents]];
        if(currentIndex == 0 || currentIndex %2 == 0)
        {
            UIViewController *nextViewController = [self pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
            viewControllers = [NSArray arrayWithObjects:currentViewController, nextViewController, nil];
        }
        else
        {
            UIViewController *previousViewController = [self pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
            viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil];
        }
        //Now, set the viewControllers property of UIPageViewController
        [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];

        return UIPageViewControllerSpineLocationMid;
    }
}
- (void)viewDidLoad
{
    [super viewDidLoad];

    appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
    //Instantiate the model array
    self.modelArray = [[NSMutableArray alloc] init];
    self.vcs = [[NSMutableArray alloc]init];

    for (int index = 1; index <= 2 ; index++)
    {
        [self.modelArray addObject:[NSString stringWithFormat:@"Page %d",index]];
    }

    //Step 1
    //Instantiate the UIPageViewController.
    self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                                                              navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
    //Step 2:
    //Assign the delegate and datasource as self.
    self.pageViewController.delegate = self;
    self.pageViewController.dataSource = self;

    //Step 3:
    //Set the initial view controllers.

    appDelegate.contentViewController.textContents = [self.modelArray objectAtIndex:0];


    NSArray *viewControllers = [NSArray arrayWithObjects:appDelegate.firstViewController,appDelegate.contentViewController,nil];
    [self.pageViewController setViewControllers:viewControllers
                                      direction:UIPageViewControllerNavigationDirectionForward
                                       animated:NO
                                     completion:nil];

    //Step 4:
    //ViewController containment steps
    //Add the pageViewController as the childViewController
    [self addChildViewController:self.pageViewController];

    //Add the view of the pageViewController to the current view
    [self.view addSubview:self.pageViewController.view];

    //Call didMoveToParentViewController: of the childViewController, the UIPageViewController instance in our case.
    [self.pageViewController didMoveToParentViewController:self];

    //Step 5:
    // set the pageViewController's frame as an inset rect.
    CGRect pageViewRect = self.view.bounds;
    pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
    self.pageViewController.view.frame = pageViewRect;

    //Step 6:
    //Assign the gestureRecognizers property of our pageViewController to our view's gestureRecognizers property.
    self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
Meet Doshi
  • 4,241
  • 10
  • 40
  • 81
user578386
  • 1,061
  • 3
  • 14
  • 37
  • 1
    You need to provide one page at a time via your dataSource if you only want to show one page, that's why your app crashes - it has two pages to show when it's expecting one (when in portrait mode I guess). Show us your delegate and dataSource code. – Filip Radelic Apr 04 '13 at 09:31
  • 1
    sure dude.......but the app creashes in viewDidLoad – user578386 Apr 04 '13 at 09:37
  • 4
    Why does `setViewControllers:direction:animated:completion` take an array if it crashes when you pass more than one? There must be some other use case for passing in an array? – Brian Morearty Nov 22 '14 at 17:27
  • 1
    @BrianMorearty `-setViewControllers:direction:animated:completion:` takes an array so that exactly two view controllers can be passed in when the spine location is set to `UIPageViewControllerSpineLocationMid`. Otherwise, only one view controller is expected. This really should be pointed out more explicitly in the documentation. – sbrun Nov 29 '18 at 17:44

4 Answers4

27

The problem is you passing an array containing two view controllers to the page view controller while it expects one at a time, change your array to be like this :

NSArray *viewControllers = @[appDelegate.firstViewController];

You will pass one of the views but viewControllerAfterViewController and viewControllerBeforeViewController will handle the rest.

Ben
  • 953
  • 12
  • 27
  • 11
    Why is the parameter for this call an array if it only expects one view controller? – Shoerob Sep 01 '15 at 03:01
  • 10
    @Shoerob According to the documentation `For transition style 'UIPageViewControllerTransitionStylePageCurl', if 'doubleSided' is 'YES' and the spine location is not 'UIPageViewControllerSpineLocationMid', two view controllers must be included, as the latter view controller is used as the back.` – Aleks N. Nov 18 '15 at 10:59
2

Ah..Finally got solution for this same issue.., it may helps you..

When we set the spine location to UIPageViewControllerSpineLocationMid, the doubleSided property of the pageViewController is automatically set to YES. This means that the content on page front will not partially show through back. But when this property is set to NO, the content on page front will partially show through back, giving the page a translucent kind of effect. So, in the portrait orientation, we have to set the value to NO, otherwise it would result in an exception.

So in your UIPageviewcontroller delegate method, in else part add this doubleSided property as YES when you return spineLocation as UIPageViewControllerSpineLocationMid

self.pageViewController.doubleSided = YES;
return UIPageViewControllerSpineLocationMid;
wesley
  • 859
  • 5
  • 15
0
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMid;

This is the solution for the exception.

In xcode it self you could find this.

Go to the UIPageViewcontroller class there you could see the explanation for this like:

@property (nonatomic, readonly) UIPageViewControllerSpineLocation spineLocation; // If transition style is 'UIPageViewControllerTransitionStylePageCurl', default is 'UIPageViewControllerSpineLocationMin', otherwise 'UIPageViewControllerSpineLocationNone'.

// Whether client content appears on both sides of each page. If 'NO', content on page front will partially show through back.
// If 'UIPageViewControllerSpineLocationMid' is set, 'doubleSided' is set to 'YES'. Setting 'NO' when spine location is mid results in an exception.
@property (nonatomic, getter=isDoubleSided) BOOL doubleSided; // Default is 'NO'.
Thanh-Nhon Nguyen
  • 3,402
  • 3
  • 28
  • 41
Mini
  • 203
  • 1
  • 5
-4

Instead of implementing a full data source, you can set the PageViewController with one view controller at a time each time the user pushes a next or back button, like

[pageViewController setViewControllers:@[contentViewController]
                             direction:UIPageViewControllerNavigationDirectionForward
                              animated:YES
                            completion:nil];

This will animate the page transition as you switch.

Dominic
  • 1
  • 1