I am using a Tab Bar Controller in my app. In the most part of the app's behavior we have one single orientation (interfaceOrientationPortrait). But i have a view that runs a video in WebView. When i am running this video, the orientation remains portrait. I want to put for all orientations.
Well, i used the return type YES in my Video's class. Nothing has changed. I changed the return type to YES in my RootViewController class. Nothing has changed.
I would like to change the orientation only for the Video.
View controllers that are part of a tab bar controller won't rotate to an orientation if any of the tab bar controllers can not rotate to that orientation (at least if the tab bar is visible). So maybe you want to update all your controllers' method in order to let the rotation happen if the web view controller is visible on the tab bar controller.
If you need a deeper explanation, just ask. Good luck!
Edit (example code)
This is what could be your rotation methods for your tab bar controller view controllers:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ((self.tabBarController.selectedIndex == kMyWebViewControllerIndex))
{
return YES;
} else
{
return (orientation == UIInterfaceOrientationPortrait);
}
}
Is your web view in a view controller inside the tabBarController?
Try showing it in a modally presented view controller, it should work
Related
I have an app with view controller based status bar appearance set to YES. Some of my views have dark, some of my views have light content, and the app has a pretty complex view controller hierarchy, but it works perfectly with subclassing and overriding the appropriate methods combined with modal views capturing presentation styles etc).
However, I need a global way to view a specific item at top (behind status bar, inside my app bounds), just like the bar like personal hotspot/ GarageBand recording/in call etc bar at the top. Because of the bar's background color, I want to override the status bar appearance while displaying the bar (which can be displayed anywhere in the app so I subclassed UIWindow and put its presentation code and view directly there). The bar displays exactly as I wanted on screens with light content status bar (as my bar's text is white and background is dark) but looks terrible on dark content status bar (and no, I can't change the colors of the bar).
How can I override the "whatever the currently presented view controller is"'s preferred status bar style globally (of course, without traversing all instances of the status bar methods in all view controllers), while still using view controller based status bar appearance? My app targets iOS 8.0+.
I've ended up in a very hacky (but working) way. It might not work in every scenario, but it worked in mine. I've kept the view as it is, and haven't touched a single view or controller.
First, I've got the topmost view controller currently being displayed. I've used the code from iPhone -- How to find topmost view controller and modified it a little to handle navigation controller and tab bar controller cases too:
+ (UIViewController*) topmostControllerForViewController:(__kindof UIViewController*)topController
{
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
if([topController isKindOfClass:[UINavigationController class]]){
UINavigationController *navController = topController;
return [self topmostControllerForViewController:navController.visibleViewController];
}
if([topController isKindOfClass:[UITabBarController class]]){
UITabBarController *tabController = topController;
return [self topmostControllerForViewController:tabController.selectedViewController];
}
return topController;
}
+ (UIViewController*) topmostController
{
__kindof UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
return [self topmostControllerForViewController:topController];
}
Then I've created a view controller without a view (view is nil). In it's init method (does not work in first call if put in viewDidLoad: as it's called inside the transition process and it's too late), I've added the following:
self.modalPresentationCapturesStatusBarAppearance = YES;
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
That code allowed my "dummy" view(less) controller to handle all the presentation context, including status bar appaearance and what happens to the other views controllers when it's presented. When presented over current context, the view controller at the back is not removed from view hierarchy. If I don't do that, it will be removed and screen will be black (as I don't have any view and I want the previous view controller to be shown).
So far so good. Then, I've displayed my bar normally, but simultaneously, presented that view controller modally without any view. Because the view controller didn't have any view and was presented over the current context, it visually didn't appear in any way, but since it was a modal presentation and the dummy view controller was set to capture presentation style, it triggered iOS to ask my app for status bar style. I've simply set up my status bar style as I've wanted in the view controller methods.
There was a little problem. When I've presented the new view controller, system added a UITransitionView on top of my previous view controller. If there was an actual view, it would be on top of the transition view. The transition view is completely transparent, but it has user interaction enabled and captured all the touch events, making my app unresponsive until I've dismissed the controller. I needed my previous view controller to receive touch events. I've dug deeper and found where modal presentation adds the transition view, and removed it when presenting the view controller after transition animation is complete:
for (UIView *view in self.subviews) {
NSString *className = NSStringFromClass([view class]);
if([className hasPrefix:#"UIT"] && className.length == 16){
//this must be UITransitionView, but I'm not using it directly since it may interfere with private API usage and get app rejected by Apple.
//now, we need to find another transition view inside this and remove it
for (UIView *innerView in view.subviews) {
className = NSStringFromClass([innerView class]);
if([className hasPrefix:#"UIT"] && className.length == 16){
//this is the transition view that we need to remove
[innerView removeFromSuperview];
}
}
}
}
Since UITransitionView is a private view type and I'm not sure if it causes a problem with App Store, I've done a heuristic check of UITransitionView by checking the first letters UIT and checking the length of the class name. It's not bulletproof, but it seems to work and unlikely to return false positive.
Everything now works as expected. It's hacky, and may break in the future, especially if modal presentation changes under the hood. But rest assured, it works.
in iOS 7 and earlier, I was able to make UI changes in viewDidLayoutSubviews like so:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if ( UIInterfaceOrientationIsLandscape(self.interfaceOrientation) ) {
//update views for landscape
}
else {
//update views for portrait
}
}
I can still do this in iOS 8 except in view controllers that are presented by other view controllers. When a view controller is presented by another view controller, this does not work, because the self.interfaceOrientation property is not correct in the presented view controller. It does not reflect the current orientation, but rather the orientation right before the device rotation.
The issue is not present if presenting a view controller on iPad with modalPresentationStyle set to UIModalPresentationPageSheet. In other words, the issue is only present when presenting a full screen view controller.
Any suggestions?
Try using this instead:
[[UIApplication sharedApplication] statusBarOrientation]
Also, if you are dropping support for iOS 7 and going 8-only, I recommend you drop interface orientation handling in favor of trait collections and size classes, if you can.
I have an iOS application and the main entry point into the storyboard is a Tabbar with with tabs. I want to support rotation but in landscape mode, each tab will look drastically different to the point I need to change the views out with others from the storyboard. At first though, I considered just switching out the entire tab bar controller when the user rotates, but I don't see how that might be accomplished. So I have two options that I can't seem to get anywhere with.
Switch each view with a segue somehow into it's landscape alternative and back. I've seen this done easily with modal views, but not with UITabbar before.
Switch out the entire tab bar somehow from the delegate so that I just have two completely separate section of my storyboard that are alike except one path is portrait and the other is landscape.
Does anyone have anything similar they are doing and could throw me a bone?
You can create a custom class for both view controllers, link them together with segoes, and then use didRotateFromInterfaceOrientation: to trigger the segues from code.
Create a class for your VC's. Add this code to the .m file:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if (fromInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || fromInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
[self performSegueWithIdentifier:#"rotateToPortrait" sender:nil];
} else {
[self performSegueWithIdentifier:#"rotateToLandscape" sender:nil];
}
}
And in your storyboard, create segues between the two views. Make their identifiers "rotateToPortrait" and "rotateToLandscape", respectively. The code you added will switch the views whenever the device is rotated.
EDIT: I think I misunderstood the question. If you want to move views around, change their size, etc. when the screen is rotated, while keeping the same navigation/tab bar state, then you can do something like this in the didRotateFromInterfaceOrientation method:
if (fromInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || fromInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
myView.frame.origin.x = aNumber;
myView.frame.size.width = anotherNumber;
// Changing the layout of the views here by resetting their origins and sizes.
// This code is called when rotated from portrait to landscape.
} else {
// Do the same thing as above, but this one handles rotation from landscape to portrait.
}
I used this code for forcing my Home screen (first screen of my application) be portrait while other screens remain supporting all orientations:
public class RltNavigationController : UINavigationController
{
public RltNavigationController () : base ()
{
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations ()
{
if(this.TopViewController is HomeScreen )
return UIInterfaceOrientationMask.Portrait ;
else
return UIInterfaceOrientationMask.AllButUpsideDown ;
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
// Return true for supported orientations
if(this.TopViewController is HomeScreen )
return (toInterfaceOrientation == UIInterfaceOrientation.Portrait );
else
return (toInterfaceOrientation != UIInterfaceOrientation.PortraitUpsideDown) ;
}
}
Now, suppose that the device is on the landscape orientation at home screen (Device is lanscape but screen just show portrait). Now if user go to other views, other views now show portrait while it should show landscape. What solution I can choose in order to load second views with theirs actual rotation?
EDIT
Thanks for all answers, Just notice that already the problem is not that I can not force the screen to be portrait. For understanding the problem please follow the scenario:
-> First screen forced to be portrait.
-> Device is landscape right and I'm in home screen(so home screen show portrait)
-> Now I switch to another screen that support all orientation
-> at another screen because the parent screen was portrait it show portrait (while because device is landscape it should show landscape)
You can also directly select from the XIB a particular viewController be Landscape or Portrait and the same loads.
You can not explicitly say, viewController be landscape and the view will be landscape. The way it works is, you ask the controller that is controller the screen, this may be a navigation controller, tab view controller, a modal, how they want to be able to rotate. If you have a navigation controller then all viewController will only have the rotation of your navigation controller.
There were a few tricks like subclassing the navigation controller and over the should auto rotate method, call [self.visibleViewController shouldAutoRotate]; which works for making screens rotate and not rotate, but if you have only 1 screen that supports all orientations and all the others do not, then you have a pushing/popping error where if you push or pop while in that different orientation the next viewController will be in that orientation.
Since you can't directly tell the rootViewController to explicitly rotate, the next best solutions are,
A: Use QuartzCore to manually rotate the view yourself
B: have a separate xib file for each orientation so when you rotate to landscape you see the landscape viewController and vice versa
The easiest way to do this is to create a custom navigation controller (subclass of UINavigationController) that inherits its rotation based on the currently visible view controller
Inside your custom navigation controller:
-(NSUInteger) supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
-(BOOL) shouldAutorotate {
return self.topViewController.shouldAutorotate;
}
Then inside any of the view controllers within that, add these methods:
-(BOOL)shouldAutorotate{
return NO;
}
-(NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
That's for a view you DON'T want to autorotate. Modify that accordingly for views you do want to rotate.
Make sure your project settings have rotation in the orientations you want enabled, and make sure to use your custom navigation controller instead of the regular one for any view hierarchies that contain multiple possible rotations.
Note that you may run into problems if a view that is rotated is popped and the previous view is not rotatable. Off the top of my head, I'm not sure whether this will work properly.
I'm building an application that supports only Landscape orientation on the iPad under iOS 4.3, though the bug was present under iOS 4.2 as well.
Several places in the application, I am showing UIViewControllers as modal views. All of them are shown using this pattern:
viewController.modalPresentationStyle = UIModalPresentationFormSheet;
viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:viewController animated:YES];
In most places, they work as expected - the modal form sheet slides in from the bottom of the screen upward.
However, in two cases, the modal form sheet slides in from the bottom left of the screen. The form sheet slides nearly all the way to the right with the bottom of the form sheet out of view. If you focus a text field and show the onscreen keyboard, the form sheet moves to the top center of the screen where you would expect it to be.
I don't think the Simulated Metrics of the XIBs affect their behaviour, but I have set the orientation for all of them (both the calling UIViewController self and the modal view controller viewController) to Landscape.
Any ideas why these two modal form sheets are behaving differently from the others?
I think here you dint return landscape orientation in shouldAutorotate function so use this code if you wan view as landscape left/right:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight||interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
// return YES;
}
Try this it may help you..
Make sure if you have a viewController embedded in a NavigationController that the navController shouldAutotorate is also set to landscape