Hooligancat
Hooligancat

Reputation: 3788

UIPageViewControllerSpineLocation Delegate Method Not Firing

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:

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... ;-)

Upvotes: 0

Views: 1973

Answers (1)

Hooligancat
Hooligancat

Reputation: 3788

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.

Upvotes: 2

Related Questions