0
votes

I've created two storyboards both identical except one contains portrait views and one contains landscape views.

I don't want to use autoresize masks because the layout of some of the views changes completely between portrait and landscape. I've manually moved controls on the view in code in the past but I was after an easier way this time :)

The closest solution I've found was from 'benyboariu' - Storyboards orientation support for xCode 4.2?

Here's the code I'm using which works fine on iOS 5.x but not iOS 6.x.

- (void)updateLandscapeView
{
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;

if (deviceOrientation == UIDeviceOrientationUnknown)
{
    if ([[UIScreen mainScreen] bounds].size.height > [[UIScreen mainScreen] bounds].size.width)
    {
        deviceOrientation = UIDeviceOrientationPortrait;
        self.appDelegate.isShowingLandscapeView = NO;
    }
    else
    {
        deviceOrientation = UIDeviceOrientationLandscapeLeft;
        self.appDelegate.isShowingLandscapeView = YES;
    }
}

if (UIDeviceOrientationIsLandscape(deviceOrientation) && !self.appDelegate.isShowingLandscapeView)
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard-Landscape" bundle:[NSBundle mainBundle]];
    UIViewController * landscape = [storyboard instantiateViewControllerWithIdentifier:@"RootViewController-Landscape"];
    self.appDelegate.isShowingLandscapeView = YES;
    [UIView transitionWithView:landscape.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
        self.view = landscape.view;
    } completion:NULL];
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && self.appDelegate.isShowingLandscapeView)
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard-Portrait" bundle:[NSBundle mainBundle]];
    UIViewController * portrait = [storyboard instantiateViewControllerWithIdentifier:@"RootViewController"];
    self.appDelegate.isShowingLandscapeView = NO;
    [UIView transitionWithView:portrait.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
        self.view = portrait.view;
    } completion:NULL];
}
}

The error I'm getting when debugging on iOS 6.x is:

Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency', reason: 'A view can only be associated with at most one view controller at a time! View UIView: 0x108276b0; frame = (0 0; 568 268); autoresize = RM+BM; animations = { position=CABasicAnimation: 0x10815c60; bounds=CABasicAnimation: 0x1082a5e0; }; layer = CALayer: 0x10827710 is associated with RootViewController: 0x10821f10. Clear this association before associating this view with RootViewController: 0x961a150.'

I usually unlink the view controller from the NIBs to fix this kind of error but can't seam to do that with storyboards.

Anyone got any ideas?

2

2 Answers

0
votes

Change your portait view controller to a normal view (take it out of the view controller). Now you should be able to instantiate it as a UIView and do the transition without worrying about it being associated with multiple view controllers.

0
votes

Well, after trying all sorts of ways to try and fix this problem I've come up with this solution which works on iOS 5.x and 6.x.

Basically, the problem appears to be because of the navigation controller, so, instead of doing

self.view = landscape.view;

or

self.view = portrait.view;

you need to replace all the view controllers contained in the navigation controllers stack.

What I'm doing in the code below is to create an NSMutableArray of the all view controllers in the navigation controller stack. Then loop each view controller and get the view tag to determine which view needs replacing. Then I simply replace the view controller with the landscape or portrait version and then set the navigation controllers view controllers array to the modified array thus replacing all the view controllers.

if (UIDeviceOrientationIsLandscape(deviceOrientation) && !self.appDelegate.isShowingLandscapeView)
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard-Landscape" bundle:[NSBundle mainBundle]];
    SearchViewController * landscape = [storyboard instantiateViewControllerWithIdentifier:@"SearchViewController-Landscape"];
    self.appDelegate.isShowingLandscapeView = YES;
    [UIView transitionWithView:landscape.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
        NSMutableArray * viewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
        if (viewControllers && [viewControllers count] > 0)
        {
            for (NSUInteger index = 0; index < [viewControllers count]; index++)
            {
                if ([[[viewControllers objectAtIndex:index] view] tag] == 1000)
                {
                    RootViewController * root = [storyboard instantiateViewControllerWithIdentifier:@"RootViewController-Landscape"];
                    [viewControllers replaceObjectAtIndex:index withObject:root];
                }
                else if ([[[viewControllers objectAtIndex:index] view] tag] == 2000)
                {
                    [viewControllers replaceObjectAtIndex:index withObject:landscape];
                }
            }

            [self.navigationController setViewControllers:viewControllers];
        }
    } completion:NULL];
}
else if (UIDeviceOrientationIsPortrait(deviceOrientation) && self.appDelegate.isShowingLandscapeView)
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard-Portrait" bundle:[NSBundle mainBundle]];
    SearchViewController * portrait = [storyboard instantiateViewControllerWithIdentifier:@"SearchViewController"];
    self.appDelegate.isShowingLandscapeView = NO;
    [UIView transitionWithView:portrait.view duration:0 options:UIViewAnimationOptionTransitionCrossDissolve|UIViewAnimationCurveEaseIn animations:^{
        NSMutableArray * viewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
        if (viewControllers && [viewControllers count] > 0)
        {
            for (NSUInteger index = 0; index < [viewControllers count]; index++)
            {
                if ([[[viewControllers objectAtIndex:index] view] tag] == 1000)
                {
                    RootViewController * root = [storyboard instantiateViewControllerWithIdentifier:@"RootViewController"];
                    [viewControllers replaceObjectAtIndex:index withObject:root];
                }
                else if ([[[viewControllers objectAtIndex:index] view] tag] == 2000)
                {
                    [viewControllers replaceObjectAtIndex:index withObject:portrait];
                }
            }

            [self.navigationController setViewControllers:viewControllers];
        }
    } completion:NULL];
}

I've expanded the code to show how to get this working on nested views in the navigation controller. If you're working on the root view controller you obviously don't need to loop through all the view controllers, just replace the view controller at index 0.

Hope this helps someone!