I have created a custom TabBarController that inherits from UIViewController (NOT UITabBarController because it is not intended for subclassing). Everything works fine except the orientation support.
My TabBarController is set as the rootViewController on my UIWindow and contains an internal array of UIViewControllers. In my case I have added UINavigationControllers as the root of each tab.
When pushing a new UIViewController to any of my UINavigationControllers in my TabBarController I get a call to shouldAutorotateToInterfaceOrientation, this is all fine because here I can set which UIViewController should support which orientation (as described in the Apple documentation).
However when I go back by pressing the back button in my UINavigationBar I do not get a call to shouldAutorotateToInterfaceOrientation hence the view we display will end up in the wrong orientation.
I have done a quick test by replacing my custom TabBarController with a UITabBarController and I get the call to shouldAutorotateToInterfaceOrientation when pressing the back button so there must be something wrong here but I cannot figure out what.
Why don't I get the calls to shouldAutorotateToInterfaceOrientation? Is the UITabBarController doing something I have missed?
Has anybody here experienced the same problem? Do you have any ideas that might be worth trying because I have run out of ideas.
EDIT
This issue is resolved by adding each UIViewController within the TabBarController as a child using the iOS5 container view controller.
Add the UIViewController as a child with this method: addChildViewController Then override automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers and return YES.
Now you should get the shouldAutorotateToInterfaceOrientation callbacks in your UIViewControllers
I have seen such problems before, and it always boiled down to adding the -shouldAutorotateToInterfaceOrientation: method to all view controllers, not just the topmost one as you might expect.
(and if you're lazy, just add a category to UIViewController)
Related
My App starts up, and I have a UITabBarController. On the first tab I have a subclass of UIViewController. It's in this class that I have my movie playing functionality. I do not know why, but from this class, I cannot presentModalViewController or presentMoviePlayerViewControllerAnimated. I can however add views as a subView. I found this post, which is essentially what I'm trying to do (present a movie player view controller): How to present MPMoviePlayerViewController from a UITabBarController?
But even keeping a reference to the UITabBarController does not work for me. I'm not really sure why this class has problems presenting a modal view controller, versus in other tabs, I am able to. Any thoughts? Thanks.
After more looking, I found that the problem is I cannot present a view modally within the viewDidLoad method.
I can't seem to find where it's actually instantiated. I looked in the myProjAppDelegate.m and saw this:
self.window.rootViewController = self.navigationController;
But it says that the window's rootViewController property is really just a UIViewController, not a UITableViewController, which is what the RootViewController.m class is a subclass of. I wrote a custom method in my RootViewController.m and tried to call it on self.navigationController in myProjAppDelegate.m and got a SIGABRT, so it seems like this is not it. Can anyone help me out?
It's not visible in code. Your MainWindow.xib contains a Window and a Navigation Controller which are connected via outlets to your AppDelegate.
Both the Window and the Navigation Controller get instantiated when the application loads the .xib files.
Inside the Window (in the MainWindow.xib) is a RootViewController, that is the RootViewController you are talking about.
Regarding the class, UITableViewController inherits from UIViewController.
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
So my app's core is a tab bar. In each of 3 tabs is a UINavigationController subclass. Each one has a different type of table in it, which when a row is tapped, a detail is shown etc.
I currently have a 3 separate subclasses of UINavigationController, one for each tab. Then when a new tab is pressed, the table's controller is pushed.
I just read that you're not supposed to subclass UINavigationController. I'm not overriding any of UINavigation Controller's functionality, but I am overriding it's UIViewController functionality in viewDidLoad. Honestly that's just about it. It seems pretty silly, but Im unclear on how to get the Navigation Controller functionality without subclassing the way I have.
So how am i supposed to have a UINavigationController that I don't subclass? What is the approach that you're supposed to take to switch out the views when a tab is selected?
I'm pretty much a noob. Will Apple reject my app for subcalssing UINavigationController if im only overriding viewdidload?
I've heard folks say not to subclass UINavigationController, and instead 'present it modally.' I have used modal presentation a little bit, but I honestly dont' quite get how it would apply...
Thanks for your help!
What are you doing in viewDidLoad? What about do it in root view controller, not in navigation controller?
P.S. I think Apple will not reject your app for subclassing UINavigationController.
You should use categories to add extra functionality like that. It would look something like this:
#implementation UINavigationController (CustomViewDidLoad)
- (void)viewDidLoad
{
//code goes here
}
#end
You can add this to the bottom of the file that initializes the navigationcontroller
More info about categories (at the bottom): http://cocoadevcentral.com/d/learn_objectivec/
Here's what I have:
A MainWindow.xib file configured with one UIViewController (subclassed to RootViewController). This nib gets loaded at application launch.
RootViewController has two ivars, a custom subclass of UIViewController and a UINavigationController. Both of these are loaded from nibs.
When the application launches both ivars are initialized from their respective nibs, and then the UIViewController.view is added as a subview of RootViewController.view.
Inside UIViewController's view I have a control that triggers an animated swap of UIViewController and UINavigationController. This is where the problem comes in. The swap animates, but the UINavigationController's views are not properly displayed. I get a Navigation Bar with no title, and nothing else.
The UINavigationController nib and underlying functionality have been tested in a stand alone project, but there was no RootViewController.
So, my question is, can I even do this? I've successfully swapped other view controllers like this, but never a UINavigationController. I've seen some documentation that leads me to believe this might be a fools errand, but I haven't convinced myself of that yet.
Solution (Kinda):
I found a solution (workaround? hack?), but it leads to some more questions. I nixed using a Nib for the UINavigationController. Instead, I loaded my UINavigationController's rootViewController from a Nib and then created the UINavigationController programmatically with initWithRootViewController:.
NavRootViewController *navRoot = [[NavRootViewController alloc] initWithNibName:#"NavRootViewController" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:navRoot];
[navRoot release];
This works as I expect. Which leads me to the conclusion that the rootViewController property of the UINavigationController wasn't being set properly when I loaded navigationController from a Nib. And the question is, why? Should it?
Also, when you see something like this happening in one case, but not another, it can be beneficial to either create a subclass and make your nib point at that subclass, or if you already have a subclass use that.
In the subclass, override all the various init:, initWithNibName:bundle:, viewDidLoad:, viewWillAppear:, viewDidAppear: and any other appropriate methods, and in those override, just NSLog("") something about which method it is (with param values perhaps) and call the super implementation.
This will give you an observable "track" of which methods are called in which order, and you can set a breakpoint to see where that call comes from.
This will give you enough information to find missing method calls, and then you can pursue the correct problem either here, or through filing a radar or ...
In some cases, viewDidLoad and viewDidAppear or awakeFromNib may need to be called each time you add the UINavigationController back into the stack of UIViewControllers. It seems that when the typical code executes out of your AppDelegate, that the Window, or something behind the scenes is doing something special for UINavigationController that presentModalViewController doesn't do.
I think you may have missed a conceptual point.
A UINavigationController controls view controllers instead of views. It controls when and where view controllers themselves are loaded. The views themselves are loaded only as a side effect of the pushing and popping of their respective controllers.
Therefore, putting a navigation controller inside of a view controller seldom makes much sense.
If I understand what you are trying to do correctly, you should have the RootController actually set as the rootController property of the UINavigationController (yes the nomenclature is extremely confusing.) Then when your swap event occurs, you should have the navigation controller push the next view. The RootController view will disappear to replaced by the other. then you can repeat the process for an arbitrary number of view controllers.
Only in the case of a tabbar would you want a navigation controller to be a property of a view controller. Even then it should be at the top the tab's hierarchy.