Obiwahn
Obiwahn

Reputation: 3087

Xcode Page Based Application Interface Rotation Issue

  1. Start a new page based application project in Xcode
  2. Run the project and turn some pages
  3. Rotate the simulator or device
  4. => The page view conroller switches back to the first page (january)

How can I prevent step 4. ?

EDIT: This happens only the first time you rotate after the app started in simulator/device. I use most recent Xcode 4.5 with iOS 6.0 Simulator and iOS 6 on my testing device. The same thing happens when I download some other sample code from blogs / etc. Maybe an iOS 6 bug?

EDIT2: I found out that the first page view that is passed to the UIPageViewController is not dealloced until first rotation. This really looks like a bug to me.

Upvotes: 1

Views: 785

Answers (5)

Andrew Burke
Andrew Burke

Reputation: 118

(UPDATE FROM 2014: This seems to have been fixed in iOS7, if you start again from a new Page View application template.)

I've experienced this bug as well. It seems to kick in any time after the main view reappears. My app has several full-screen modals in it, and after those go away the same behaviour occurs.

This happens in XCode 4.5.1 and iOS6 - I 'fixed' this by re-downloading XCode 4.4 and reverting my app back to iOS5.1. Obviously not a great long-term solution. I filed this in Radar and got a note back that it was already logged.

FWIW I noticed that iBooks had this same bug in it right after iOS6 came out, but they seem to have fixed it in a recent update.

Upvotes: 4

Gerd Moe-Behrens
Gerd Moe-Behrens

Reputation: 11

Here is my solution:


//  RootViewController.m

#import "RootViewController.h"

#import "ModelController.h"

#import "DataViewController.h"

@interface RootViewController ()
@property (readonly, strong, nonatomic) ModelController *modelController;

//added
@property (strong, nonatomic) DataViewController *currentViewController;
@end

@implementation RootViewController

@synthesize modelController = _modelController;

//added
@synthesize currentViewController = _currentViewController;

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
    // Configure the page view controller and add it as a child view controller.
    self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
    self.pageViewController.delegate = self;

    DataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
    NSArray *viewControllers = @[startingViewController];
    [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];

    self.pageViewController.dataSource = self.modelController;

    [self addChildViewController:self.pageViewController];
    [self.view addSubview:self.pageViewController.view];

    // Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
    CGRect pageViewRect = self.view.bounds;
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
    }
    self.pageViewController.view.frame = pageViewRect;

    [self.pageViewController didMoveToParentViewController:self];

    // Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
    self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;

    //added
    self.currentViewController = self.pageViewController.viewControllers[0];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (ModelController *)modelController
{
     // Return the model controller object, creating it if necessary.
     // In more complex implementations, the model controller may be passed to the view controller.
    if (!_modelController) {
        _modelController = [[ModelController alloc] init];
    }
    return _modelController;
}

#pragma mark - UIPageViewController delegate methods

/*
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{

}
 */


//added
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

{
    self.currentViewController = self.pageViewController.viewControllers[0];

}

- (DataViewController *)currentViewController
{
    if (!_currentViewController) _currentViewController = [[DataViewController alloc] init];
    return _currentViewController;
}



- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
    if (UIInterfaceOrientationIsPortrait(orientation) || ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)) {
        // In portrait orientation or on iPhone: Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to YES, so set it to NO here.



        //deleted: UIViewController *currentViewController = self.pageViewController.viewControllers[0];
        //changed to self.currentViewController
        NSArray *viewControllers = @[self.currentViewController];
        [self.pageViewController setViewControllers:viewControllers
                                          direction:UIPageViewControllerNavigationDirectionForward
                                          animated:YES
                                          completion:NULL];

        self.pageViewController.doubleSided = NO;
        return UIPageViewControllerSpineLocationMin;
    }

    // In landscape orientation: Set set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers.
   // deleted:  DataViewController *currentViewController = self.pageViewController.viewControllers[0];
   //deleted: NSArray *viewControllers = nil;
    //added
     NSArray *viewControllers = @[self.currentViewController];

   //changed currentViewController to self.currentViewController
    NSUInteger indexOfCurrentViewController = [self.modelController indexOfViewController:self.currentViewController];

    if (indexOfCurrentViewController == 0 || indexOfCurrentViewController % 2 == 0) {
        UIViewController *nextViewController = [self.modelController pageViewController:self.pageViewController viewControllerAfterViewController:self.currentViewController];
        viewControllers = @[self.currentViewController, nextViewController];
    } else {
        UIViewController *previousViewController = [self.modelController pageViewController:self.pageViewController viewControllerBeforeViewController:self.currentViewController];
        viewControllers = @[previousViewController, self.currentViewController];
    }
    [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];


    return UIPageViewControllerSpineLocationMid;
}

@end

Upvotes: 1

Obiwahn
Obiwahn

Reputation: 3087

Today I found out that in my app I could just use the following to remove the bug (but I have no clue why).

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
        ...
         self.pageViewController.view.hidden = YES;
    }

-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
        self.pageViewController.view.hidden = NO;
    }

Upvotes: 0

Andrew Burke
Andrew Burke

Reputation: 118

Here's how I managed to fix this problem in my app. I'm afraid it's kind of a hacky solution, but it's a quirky bug.

Context: My app is a diary (it's called Remembary) and each page is a different day's diary entry. I have a singleton class called "AppContext" that keeps track of various app-level values, such as the currently showing diary entry object, the current date, and the like. Each day's dataViewController also keeps track of its own diary entry.

The trickiest part was finding a context where I could catch that the app was showing the wrong page. It turns out that this is in [RootViewController viewDidLayoutSubviews], so I added the following to that method:

// get the currently displaying page
DataViewController *currentPage = self.pageViewController.viewControllers[0];
// check if we're showing the wrong page
if ([currentPage myEntry] != [AppContext getCurrentEntry]) {        
    // jump to the proper page (the delay is needed to ensure that the rotation has fully completed)
    [self performSelector:@selector(forceJumpToDate:) 
               withObject:[AppContext getCurrentEntryDate] 
               afterDelay:0.5];
}

Here's the forceJumpToDate function, which basically gets a new page based on the current date and tells the pageViewController to jump to it without animating:

- (void) forceJumpToDate:(NSDate *)targetDate {
    DataViewController *targetPage = [self.modelController viewControllerForDate:targetDate 
                                                                      storyboard:self.storyboard];
    NSArray *viewControllers = [NSArray arrayWithObject:targetPage];
    [self.pageViewController setViewControllers:viewControllers 
                                      direction:UIPageViewControllerNavigationDirectionForward 
                                       animated:NO 
                                     completion:NULL];
}

The user might notice a brief hiccup on the screen as the new page is forced into place, but this only happens if they would otherwise be getting the wrong page, so it's still an improvement.

This was seriously interfering with my ability to upgrade my app to iOS6, so I'm glad I finally figured it out.

Upvotes: 1

PaulPerry
PaulPerry

Reputation: 906

What is it you want to prevent? Do you want to prevent rotation? If that is what you want, modify the shouldAutorotateToInterfaceOrientation return value in the RootViewController.m implementation file.

When I did this, the App was able to keep the same page (month) even after rotating the device. I used the simulator and tried on both iPhone and iPad. On the iPad, in landscape mode, it showed two months at a time, but then when rotated back to portrait, still kept the first of the two months that was displayed. This was when I incremented to June. I used the default project without changing a line of code.

Upvotes: 0

Related Questions