Selective Autorotation within a UINavigationContro

2019-01-19 05:18发布

问题:

Greetings! Here's the scenario.

Starting with a navigation controller (and no tab bar is present - it is hidden from a previous view controller push), I init a new view controller and push it onto the nav controller stack. This new VC contains a lonesome UIView into which I programmatically add a UIScrollView with the same frame. (I wanted to avoid the UIView, but this was the only way I could get self.view to be assigned to something. I suspect casting a UIScrollView to UIView in viewDidLoad is not advisable.)

So now we have a nav bar, and a scroll view. I've set it up to scroll through some images (big surprise, I know!), and that works just fine. Now I want this to support autorotation. So I respond in the VC like so:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

Compile and run. Aaaand ... nothing. Obviously I've done something wrong.

Now, I've already read the post regarding UINavigationController and autorotation, and I get the sneaking suspicion that I'm going about this the wrong way, and making it way more complicated than necessary.

There's got to be a better way to present a UIScrollView that supports autorotation. Perhaps the Nav Controller is getting in the way, but I'm not sure how to get around it.

Ideally, I'd like something without any kind of nav bar showing. Instead, we have a toolbar/status bar that appears/hides from the top (like you see when playing video). If the nav bar must remain - or if that's REALLY a shorter-height nav bar I'm seeing when playing video vs. a toolbar, however do I get the thing to rotate around? The thing is, I only want it to rotate in this particular mode, when viewing the pix. Not at any other time.

Dare I try using a modal VC? (Yeccch - no, that can't be right either. Plus it has a nav bar anyway.)

回答1:

You can solve this without subclassing by creating a UITabBarController category.

Here is the category implementation which handles my case where I have anonymous UINavigationControllers associated with my tabs and custom UIViewController subclasses as the root view controllers of the UINavigationControllers:

@implementation UITabBarController (Rotation)

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 
{   
    if ([self.selectedViewController isKindOfClass:[UINavigationController class]]) {
        UIViewController *rootController = [((UINavigationController *)self.selectedViewController).viewControllers objectAtIndex:0];
        return [rootController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
    }
    return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}

@end

I also have a category on UINavigationController which defaults to returning YES. So, the default behavior is to enable rotation and I can return NO from shouldAutorotateToInterfaceOrientation:interfaceOrientation for just the controllers and orientations for which I wish to disable rotation.



回答2:

Yep - it was easier than I thought!

True, no tab bar is visible ... but this is still, at the core, a tab bar-based app.

Unless the UITabBarController allows autorotation, all bets are off for any other views. So, it's simply a matter of subclassing UITabBarController and responding appropriately to shouldAutorotateToInterfaceOrientation: (vs. making it part of the App Delegate as Xcode does by default).

How do you do that? Glad you asked. Follow these steps only if you've created a tab bar controller app using the Xcode defaults. (BACKUP your work before trying this! Disclaimer disclaimer, yadda yadda.)

  1. Create a new UIViewController subclass (we'll call it VC). Adjust it to be a subclass of a UITabBarController with an explicit delegate of UITabBarControllerDelegate.
  2. Transplant all the Tab Bar delegate bits from your App Delegate into this new VC.
  3. In your new VC's viewDidLoad method, add self.delegate = self; to the end.
  4. In MainWindow.xib (or wherever your tab bar controller and tab bar are defined), pick your Tab Bar Controller object and go to the Identity Inspector (Cmd-4). Change the class to your new VC instead of the standard UITabBarController class.

Now we're in business. Just add this to the new VC source:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES; // Adjust to taste
}

Why the delegate stuff? The IB file's owner is UIApplication, and I couldn't tie the delegate to my new VC via IB. Since I want the chance to respond to delegate methods, I added it in explicitly. If you don't need that, it's OK to leave it out. (If this can be done in IB, someone please chime in!)

The only remaining trick is setting this to YES selectively. You may not want to support autorotation all of the time (as is my case). Here's how I do it. First, I change that newly-added method (from above) ... to this:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {   
    return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}

Now I can respond to this same method from any of my app's VCs, and it will bubble up to the Tab Bar controller! For instance, in my app, I have a VC that I only want to show in Portrait, so I respond like so:

return (interfaceOrientation == UIInterfaceOrientationPortrait);

However, this same VC can take the user to a photo gallery, for which I do want to allow some rotation. In that VC's autorotate method, I respond differently:

return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

Now the gallery view will allow autorotation to all orientations except upside-down portrait, plus when I go back up the view controller chain, the orientation reverts to portrait. :)



回答3:

It can be simpler still:

1) Subclass UITabBarController and implement the shouldAutorotate... as you described (second code snippet)

2) Change your xxxAppDelegate.h to have the class of the UITabBarController changed to the subclass you just created. (use #import YourNewTabBarController.h)

3) In MainWindow.xib change the class of the tab bar controller to your new class.

Presto!

PS: YourNewTabBarController should ONLY implement the shouldAutoRotate.... Remove all other (auto generated) stuff.