0

Major head-scratcher all day on this one :-(

I have an instance of a UIPageViewController that does not appear to be firing the delegate method:

-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
                  spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation

I have tried various methods of displaying the UIPageViewController and have settled on a programatic approach (as opposed to a Storyboard one) that appears to be working correctly, with one exception... when rotating the iPad to landscape the spine does not appear mid-point as expected. I simply cannot find out why the delegate method does not get called.

Code Explanation (simplified for example)

Consider three classes as follows:

  • RootViewController - loaded when the app starts
  • PageViewController - loaded by RootViewController upon user initiation
  • PageContentViewController - loaded by PageViewController when pages are needed

Fairly self-explanatory. The RootViewController is loaded by the app upon launch. When the user taps an image within this view controller's view (think magazine cover opening a magazine) it launches the PageViewController as follows:

PageViewController *pvc = [[PageViewController alloc] initWithNibName:@"PageView" 
                                           bundle:[NSBundle mainBundle]];
pvc.view.frame = self.view.bounds;
[self.view addSubview:pvc.view];

In the actual app there is animation etc to make the transition all nice, but essentially the PageViewController's view is loaded and takes fullscreen.

PageViewController

This is the workhorse (only relevant methods shown). I have tried various examples from the infinite world of Google and written directly from the Apple docs...

@interface PageViewController : UIViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>

@property (nonatomic, strong) UIPageViewController *pageViewController;
@property (nonatomic, strong) NSMutableArray *modelArray;

@end


@implementation TXCategoryController

-(void)viewDidLoad 
{

    [super viewDidLoad];

    // Simple model for demo
    self.modelArray = [NSMutableArray alloc] init];

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

    self.pageViewController = [[UIPageViewController alloc] 
               initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                 navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];

    self.pageViewController.delegate = self;
    self.pageViewController.dataSource = self;

    PageContentViewController *startupVC = [[PageContentViewController alloc] initWithNibName:@"PageContent" bundle:nil];

    startupVC.pageLabel = [self.modelArray objectAtIndex:0];

    [self.pageViewController setViewControllers:[NSArray arrayWithObject:startupVC]
                                      direction:UIPageViewControllerNavigationDirectionForward
                                       animated:NO
                                     completion:nil];

    [self addChildViewController:self.pageViewController];
    [self.view addSubview:self.pageViewController.view];
    [self.pageViewController didMoveToParentViewController:self];
    self.pageViewController.view.frame = self.view.bounds;
    self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

}

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
  viewControllerBeforeViewController:(UIViewController *)viewController
{
    // Relevant code to add another view...
}

-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
   viewControllerAfterViewController:(UIViewController *)viewController
{
    // Relevant code to add another view...
}

-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
                  spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation

{
    // Setting a break point in here - never gets called

    if (UIInterfaceOrientationIsPortrait(orientation))
    {
        // Relevant code to create view...
        return UIPageViewControllerSpineLocationMin;
    }

    // Relevant code to create 2 views for side-by-side display and
    // set those views using self.pageViewController setViewControllers:

    return UIPageViewControllerSpineLocationMid
}

@end

This all works perfectly well as I mentioned earlier. The PageViewController's view gets shown. I can swipe pages left and right in both portrait and landscape and the respective page number appears. However, I don't ever see two pages side-by-side in landscape view. Setting a breakpoint in the spineLocationForInterfaceOrientation delegate method never gets called.

This is such a head-scratcher I have burned out of ideas on how to debug/solve the problem. It almost behaves like the UIPageViewController isn't responding to the orientation changes of the device and therefore isn't firing off the delegate method. However, the view gets resized correctly (but that could be just the UIView autoresizing masks handling that change).

If I create a brand new project with just this code (and appropriate XIb's etc) it works perfectly fine. So something somewhere in my actual project is causing this. I have no idea where to continue looking.

As usual, any and all help would be very much appreciated.

Side Note

I wanted to add the tag 'uipageviewcontrollerspinelocation' but couldn't because it was too long and I didn't have enough reputation (1500 required). I think this is a devious ploy on Apple's part to avoid certain tags in Stackoverflow... ;-)

Hooligancat
  • 3,588
  • 1
  • 37
  • 55
  • Do you have the transition style set to UIPageViewControllerTransitionStylePageCurl ? – rdelmar Aug 10 '12 at 03:58
  • @rdelmar - I do. I noticed in the docs that it said you can kiss goodbye to a two-page spread if you omit this key setting... – Hooligancat Aug 10 '12 at 04:19
  • If you have it working in a new project you could diff the XIB files and see if there is a setting that you accidentally tripped in IB – deleted_user Aug 10 '12 at 05:44
  • @stackmonster - Good suggestion, unfortunately my actual project was XIB file was way too big compared to the test project. The issue turned out to be something else (see my answer), but I appreciate your suggestion. – Hooligancat Aug 13 '12 at 17:49

1 Answers1

2

Finally found the problem. It was something of a red herring in its symptoms, but related just the same.

Putting a break point in the shouldAutorotateToInterfaceOrientation: method was a natural test to see if the UIViewController was even getting a rotation notification. It wasn't which led me to Apple's technical Q&A on the issue: http://developer.apple.com/library/ios/#qa/qa1688/_index.html

The most relevant point in there was:

The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.

Unfortunately, Apple, in its traditional documentation style, doesn't provide an answer, merely confirmation of the problem. But an answer on Stack Overflow yielded the next clue:

Animate change of view controllers without using navigation controller stack, subviews or modal controllers?

Although my RootViewController was loading the PageViewController, I was doing it as a subview to the main view. This meant I had two UIViewController's in which only the parent would respond to changes.

The solution to get the PageViewController to listen to the orientation changes (thus triggering the associated spine delegate method) was to remove addSubview: and instead present the view controller from RootViewController:

[self presentViewController:pac animated:YES completion:NULL];

Once that was done, the orientation changes were being picked up and the PageViewController was firing the delegate method for spine position. Only one minor detail to consider. If the view was launched in landscape, the view was still displaying portrait until rotated to portrait and back to landscape.

That was easily tweaked by editing viewDidLoad as follows:

PageContentViewController *page1 = [[PageContentViewController alloc] initWithNibName:@"PageContent" bundle:nil];

NSDictionary *pageViewOptions = nil;
NSMutableArray *pagesArray = [NSMutableArray array];

if (IS_IPAD && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
    pageViewOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationMid]
                                                  forKey:UIPageViewControllerOptionSpineLocationKey];

    PageContentViewController *page2 = [[PageContentViewController alloc] initWithNibName:@"PageContent" bundle:nil];

    [pagesArray addObject:page1];
    [pagesArray addObject:page2];
}
else
{
    [pagesArray addObject:page1];
}


self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                                                          navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
                                                                        options:pageViewOptions];
self.pageViewController.delegate = self;

[self.pageViewController setViewControllers:pagesArray
                                  direction:UIPageViewControllerNavigationDirectionForward
                                   animated:NO
                                 completion:NULL];

Job done and problem solved.

Community
  • 1
  • 1
Hooligancat
  • 3,588
  • 1
  • 37
  • 55
  • Just for someone coming here I'd like to add that your findings are related with child view controllers. The UIPageViewController is a good example of view controllers containment and make use of most of this, mainly the orientation changes events being forwarded to the child controllers. – Fábio Oliveira May 14 '13 at 15:26