开发者

iPhone - Working with Orientation and Multiple Views

I searched long and hard in an effort to figure this out. It took about 4-5 hours before I finally managed to reach a solution. I will be answering my own question in an effort to provide support to anyone who comes to the same problem.

So, I created the application Critical Mass Typer. It is a typing game that utilizes the keyboard as the main method of control. Words move towards the center of the screen where you have a nucleus. You type the word, and blow the word away before it reaches your nucleus. If 4 words make it to your nucleus, you reach critical mass, and explode (game over).

Now, I wanted to be able to support both landscape and portrait mode in the game. I thought about it ahead of time, and designed my classes such that it would be easy to implement. Setting up the game to do so was not a problem. Getting the views to rotate correctly was.

One thing that came up rather quickly was: any view that returns NO for the shouldAutorotateToInterfaceOrientation method of a view contro开发者_Go百科ller will cancel out the returns of its subviews.

So, my main view controller (CriticalMassViewController) controls the launch/menu of the game, and adds, as a subview, a view controller that runs a game mode (CMGameViewController). I only want my main view controller to be in landscape mode because if I rotate it, the animation/buttons all run off the side. I would have to create another view or alter the position of everything on the screen if I wanted it to be set up for portrait mode. However, my game view needs to be able to switch. To accomplish this, you set the return value to the specific mode you want your main view to be. Then tell your game view to not return any mode. After that, register the game view for notifications, then handle the rotation on your own.

For Ex: CriticalMassViewController.m:

    -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}

CMGameViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    /*GUI setup code was here*/

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

-(void)didRotate:(NSNotification *)nsn_notification {
    UIInterfaceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];
    /*Rotation Code
    if(interfaceOrientation == UIInterfaceOrientationPortrait) {
        //Portrait setup
    } else if(interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
        //Landscape setup
    }

    */
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
        // Return YES for supported orientations
        return NO; 
    }

Ok, so what's happening here? My main view controller only wants to be in LandscapeRight mode. So it stays like that since it never registers for any other orientation to auto rotate to. My game view controller needs to be able to rotate. If I use the following code for shouldAutorotateToInterfaceOrientation

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
        return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
    }

IT WILL NOT ROTATE WHEN THE IPHONE CHANGES ORIENTATION. This is because its super view says it doesn't want to rotate. Once the super view returns no, the subview doesn't even get asked. So instead, we the game view controller just says No, i dont want to autorotate to any view. Then we register for the notification of an orientation change, and call our own rotate method.

These last bits of code are how you handle the rotation yourself. Call rotateView from inside didRotate. Note, I only want the user to be able to rotate after a difficulty is chosen, so I have logic inside my didRotate method to determine if it should even rotate. The comments in the code will explain why the certain things are happening.

-(void) rotateView:(UIInterfaceOrientation)uiio_orientation {

    /*This line is very important if you are using the keyboard. This was actually the
    largest problem I had during this entire ordeal. If you switch orientations while
    the keyboard is already being displayed, it will stay in landscape orientation. To
    get it to switch to portrait, we have to tell the status bar that its orientation
    has changed. Once you do this, the keyboard switches to portrait*/
    [UIApplication sharedApplication].statusBarOrientation = uiio_orientation;

    //Get the view you need to rotate
    UIView *portraitImageView = self.view;

    /*Check to make sure that you are in an orientation that you want to adjust to. If
    you don't check, you can make your view accidently rotate when the user orientates
    their phone to a position you aren't expecting*/
    if(uiio_orientation == UIInterfaceOrientationPortrait || uiio_orientation == UIInterfaceOrientationLandscapeRight) {
        if(uiio_orientation == UIInterfaceOrientationPortrait) {
                    //This transform rotates the view 90 degrees counter clock wise.
            portraitImageView.transform = CGAffineTransformMakeRotation(-M_PI/2);
        } else if(uiio_orientation == UIInterfaceOrientationLandscapeRight) {
                    //This transform rotates the view back to its original position
            portraitImageView.transform = CGAffineTransformMakeRotation(0);
        }

        CGRect frame = portraitImageView.frame;
        frame.origin.y = 0;
        frame.origin.x = 0;
        frame.size.width = portraitImageView.frame.size.height;
        frame.size.height = portraitImageView.frame.size.width;
        portraitImageView.frame = frame;
    }
}

I hope this helps someone, I know I had a very tough time with it, especially the keyboard going from landscape to portrait.


User written UIViewControllers are not meant to be nested on a single screen. I believe the Apple line is "one UIViewController per screen." Orientation change issues like this are one of the big reasons. This is an understandable "mistake", one I'm tempted to make myself in some cases: the temptation is to use a UIViewController as a UIView loader in this case. There's no real reason to use a UIViewController at all as a nested or reusable "widget" component in a screen, just to get to its view, you don't really get any benefit, because all of those useful handy functions like the orientation change, viewWillAppear, viewWillDisappear, will not be called automatically for you by any other UIViewController other than your own! These calls are not "broadcast" but "forwarded" from UIViewController parent to UIViewController child. And if you write your own UIViewController subclass, you're responsible for passing down any of these inherited method calls.

Your inner UIViewController was not "not called" because the outer UIViewController answered NO, it was not called because you did not forward the call to your nested UIViewController yourself. The orientation change method shouldAutorotateToInterfaceOrientation, just like the other viewWill* methods, are not broadcast to all UIViewControllers, they are passed down the hierarchy from the top level window, from class to class. Built-in UIViewControllers like UINavigationControllers and UITabBarControllers explicitly pass down these calls to their children, that's why it seems like a broadcast when they arrive but when you insert your own UINavigationController subclass to the VC hierarchy, you must yourself pass down these calls to any children UINavigationControllers. This is what you must do if you want to write your own "global controller VC" like a UITabBarController replacement.


 if([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait || [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)
        {         
           //for portrait
             NSLog(@"portrait");
        }
        else if ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft || [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight)
        {
            // code for landscape orientation      

             NSLog(@"landscape");
        }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜