Selective Autorotation within a UINavigationController and UITabBarController - iphone

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

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.

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.)
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.
Transplant all the Tab Bar delegate bits from your App Delegate into this new VC.
In your new VC's viewDidLoad method, add self.delegate = self; to the end.
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. :)

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.

Related

Supporting rotation on only a child UIViewController in a hierarchy?

I have a child UIViewController with that's part of a hierarchy with a UITabBarController and a UINavigationBarController. Let's call it ChildViewController; then my hierarchy looks like:
UITabBarController
|
UINavigationViewController [tab 1]
|
SomeParentViewController
|
SomeOtherParentViewController
|
ChildViewController
Now I want only ChildViewController to support rotation to landscape orientation. (It's a view controller that shows a chat view, and the landscape mode is easier for typing for some.) I added method - (BOOL) shouldAutorotateToInterfaceOrientation: to ChildViewController to declare that it supports landscape orientation, but rotating the device had no effect. From debugging, I found that – willAnimateRotationToInterfaceOrientation:duration: wasn't being called.
After some searching around online, I've found that a descendent of a UITabBarController only supports a given orientation if the UITabBarController itself supports that orientation. And, strangely enough, UITabBarController only supports an orientation if the view controllers for each of its tabs support rotation. Like tab 1 above, the view controllers for the other three tabs are UINavigationViewController instances; and, because we must go deeper, each UINavigationViewController only supports orientation if its child view controller supports the orientation.
So at this point, adding adding - (BOOL) shouldAutorotateToInterfaceOrientation: to SomeParentViewController and the children of the other UINavigationController instances allowed ChildViewController to rotate. But now SomeParentViewController and the other three tabs will rotate to landscape, and it looks horrible. I only wanted ChildViewController to support landscape.
As a latch ditch effort, I created my own UITabBarController subclass called RotatingUITabBarController and add a global flag to the ChildViewController class that lets me know if it has been created and is displayed. The RotatingUITabBarController overrides only - (BOOL) shouldAutorotateToInterfaceOrientation: and is implemented as:
if ([ChildViewController isDisplayed]) {
return ((toInterfaceOrientation == UIInterfaceOrientationPortrait) ||
(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) ||
(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight));
}
return NO;
Now, if I boot the app, switching to SomeParentViewController or any other tab and rotating the phone does not switch to landscape mode, instead keeping in portrait. So far so good. If I create and display ChildViewController and rotate the phone, it enters landscape. So far so good. But now if I pop ChildViewController to reveal SomeOtherParentViewController, it is also in landscape. And so is SomeParentViewController and every other tab that I switch to.
I'm out of tricks now. Any advice would be much appreciated, thanks!
Perhaps the best model for the kind of behavior you seem to want is the YouTube app. Most the interface is portrait-only, but the view that plays videos works in either portrait or landscape.
If you look at that app, you'll notice that the whole tabbed part of the UI is actually a modal view controller. When you launch the app, the tab bar controller is immediately presented modally. The only time you leave that modal tab bar controller is when you play a video -- you'll notice that the whole tabbed interface slides down to reveal the video view. When the video ends, the tab bar controller is again presented modally.
This is an inversion of the "normal" approach, where you use a modal view controller only briefly, but it works very well in the YouTube app. It may or may not work well for you too. The important thing is to make your app predictable and fluid, and make the user feel in control at all times.

iOS: request for up to date solution for UITabBarController rotation

What is up to date best solution for rotating inner views when dealing with UITabBarController? I'm playing around with a standard app view hierarchy: In my main app delegate file, I'm creating UITabBarController, then, I'm creating UINavigationController, filling it with a UITableViewController (with instantiated custom subclass), and adding that UINavigationController to the first tab bar item. Now, I need UITableViewController to autorotate. I know that I need to implement shouldAutorotateToInterfaceOrientation in all view controllers, therefore, I implemented it in my custom UITableViewController subclass implementation file:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
But the UITableViewController is not rotating :( .Now, I understand that there are UINavigationController and UITabBarController objects, that are sitting above my TVC but those two are instantiated directly not through a subclass, so there is no place where to return yes for autorotation. However, I'm able to solve this problem by subclassing a UITabBarController and implementing shouldAutorotateToInterfaceOrientation method in its implementation file. But I have read, that this is a non-recommended approach and my conscience feels bad :) Another working solution is to implement a category for UITabBarController (http://stackoverflow.com/questions/1269704/uitabbarcontroller-morenavigationcontroller-and-the-holy-grail-of-device-rotatio)...
So, these two solutions are the only ones that I was able to apply. Are there any other "out of the box" solutions, for example, setting some property on the UITabBarController or smth?
In the class which you either subclass UITableViewController or implement UITableView in UIViewController, you need to set autoresizingMask for tableView. It is something like
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
and TableView should be ok and rotated properly.
Hope this help :)

Rotating view controllers within a hierarchy of Tab Bar Controller -> Navigation Controller -> View Controller

My app has a view controller hierarchy set up like this:
UITabBarController
|
UINavigationController
| |
| UIViewController
|
UINavigationController
|
UIViewController
All of my view controllers that are within this hierarchy override the method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
and return YES - therefore the view controller should be able to rotate to any rotation - even upside down.
However, within this setup none of the view controllers successfully rotate. I was under the impression that navigation and tab bar controllers would rotate if their view controllers respond to rotating.
Why won't my view controllers rotate?
The only way I've been able to get them to rotate is by subclassing UINavigationController and overriding it's shouldAutorotate method, but this feels unnecessary to me and I was wondering if there's something I've missed to make this work.
Edit:
According to the User Experience Coding How-to:
If you are also using a toolbar, the view controller for each toolbar item must implement the shouldAutorotateToInterfaceOrientation: method and return YES for each of the orientations you wish to support. If you have a navigation controller for a toolbar item, the root view controller of that navigation controller must implement the shouldAutorotateToInterfaceOrientation: method and return YES.
It says 'toolbar' - but I think this is a typo and is probably supposed to be 'tab bar'.
So it seems that I'm implementing this correctly, yet my controllers still do not auto rotate.
I've run into this problem, but I can't remember the exact reason it occurred. The tab bar controller requires all of its view controllers to respond YES when asked about a particular orientation for it to rotate to that orientation.
If presented modally, it seems like it doesn't matter about the underlying VC system.
I have created a test to show this (RotationTest on GitHub), but it all seems to be working. Hopefully I can remember why I was failing with this one at some point.
Have you tried subclassing the Tabbarcontroller and setting it as your tabbarcontroller? In there, set
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
to
YES
The only way I've been able to get them to rotate is by subclassing UINavigationController and overriding it's shouldAutorotate method, but this feels unnecessary to me and I was wondering if there's something I've missed to make this work.
I don't know for sure if this is the wrong approach, but I would subclass the UITabBarController, sooner than the UINavigationController. Also, you can try wrapping everything in a subclassed UIViewController that implements the rotation method, but this will create the overhead of an extra view. I once tried to do rotation in an with UINavigationController, but it was not pretty. I suspect that the reason the views only rotate if you subclass the UINavigationController is that the view hierarchy will only pass the rotation if the parent rotates. If the parent doesn't rotate, the child won't. (Imagine an iPhone in a dock. The iPhone only can rotate if the dock rotates. Now, compare the dock to an iPhone case. The case can also rotate, so the iPhone will rotate too.)
It says 'toolbar' - but I think this is a typo and is probably supposed to be 'tab bar'.
I do not think that the HIG has a typo in that regard. The terms may interchangeable.
Generally, a "toolbar" is relevant to the view that contains it, and therefore should rotate with its parent view. A tab bar, however, is the "parent", so to speak, of the view controller on the screen. The view controller should therefore only rotate if the entire app rotates. This concept basically boils down to this: Which view (bar or view controller) is dependent on the other? (The tab bar is persistent, but the views change, or is the toolbar only there if the view is visible.)
Subclass the UITabBarController as well as the UINavigationController. It works as using xCode 4.4.
I have developed an extension that allows you to do just this without subclassing UITabBarController https://github.com/piercifani/TabBarBetterRotation

UIView subview doesn't change orientation

I have a view controller which manages a view.
I'm adding the my view controller subclass as a subview of the window swapping out another view.
I'm running landscape mode on an iPad.
The view apparently doesn't know that its in landscape mode. Its frame is confused.
Is there something I can/should do to tell it that its in landscape, and/or that the orientation has changed. How does this normally happen. Why isn't it happening?
I used to have my view controller within a UITabBarController and it worked fine there.
Override:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return YES;
}
Your ViewController is not getting rotation events because you have not presented the viewController but have added the viewController's view in the view hierarchy.
Your Tab bar controller previously used to take the responsibility to forward the rotation events to the view controller which it manages, that was how it used to work.
I would though suggest that swapping the view out of window is a bad idea. Instead you should have a main viewController which accepts the rotation events and then swap the view within this viewController based on the current orientation. Consider re-desiging before you code further.
My problem was that my storyboard was overriding my existing custom coded app delegate. After I deleted the story board file, and custom generated view controller code, it worked for me.

Problems with Interface Orientation and UITabBarController

Quick problem:
I have an UITabBarController with 2 navigation controllers [lets call them Left and Right Controller]
On the default selected Left Controller I can push a new View Controller that detects interface orientation.
On the Right Controller I can push the same View Controller but it won't detect interface orientation, or for that matter, It won't even go into the shouldAutoRotateInterface method at all T___T
Haaalp!!
If it is of any relevance, the View Contoller that I'm pushing use the hidesBottomBarWhenPushed property.
Most likely this is your problem:
Tab bar controllers support a portrait
orientation by default and do not
rotate to a landscape orientation
unless all of the root view controllers support such an orientation.
When a device orientation
change occurs, the tab bar controller
queries its array of view controllers.
If any one of them does not support
the orientation, the tab bar
controller does not change its
orientation.
The solution is to override the following method on every view controller leading to your view:
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation {
return YES;
}
For example, instead using the default UITabBarController in IB, replace it with your own subclass containing just the method above.
I'm a bit late to the party on this, but I ran into a problem with autorotation at startup for a tab bar app I wanted always to run in portrait.
The app's plist has the necessary settings to both start in and only allow portrait mode, and all my view controllers only allow portrait mode. Yet, when I started the app holding my iPhone in landscape, the app started in portrait, but then rotated to landscape!
Rather than subclass UITabBarController, I simply overrode UITabBarController's shouldAutorotateToInterfaceOrientation: method using a category on class UITabBarController. I included this code in my app delegate:
#implementation UITabBarController(UITabBarControllerCategory)
-(BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
{
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
Works beautifully, and is quite lightweight.
does your uitabbarcontroller implement the auto rotate? any child viewcontroller that wants to implement autorotate has to have its parent implement autorotate.