I have an app that supports all orientation, but in certain cases I would like to temporarily restrict it to only portrait. It's not a mix of view controllers that need to be only portrait and those that can be any orientation, rather, I would like to disable orientation changes when the user clicks a button on screen, while the view controller stays the same.
When looking for ways to restrict UI orientation of a UIViewController, methods that pop up are supportedInterfaceOrientations() and preferredInterfaceOrientationForPresentation(). Both don't quite work for me:
supportedInterfaceOrientations() runs only once when the view is loaded, so even if I make its return value conditional, it does not run after the condition changes.
my view controllers are presented inside a navigation stack, not modally, so preferredInterfaceOrientationForPresentation() does not run at all.
Is there any way to achieve the effect I'm looking for?
I'm going to assume your view controllers are in a navigation controller. Without the navigation controller, supportedInterfaceOrientations() is called whenever the device is rotated. HOWEVER, when embedded in a navigation controller, the navigation controller decides whether the view controller should rotate. (See examples below)
What do you need to do? Implement supportedInterfaceOrientations() in a custom UINavigationController like so:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if self.topViewController is RootViewController {
return .Portrait
} else {
return .All
}
}
This is a duplicate question of this but I didn't see a swift example. For whatever class you want to stay in portrait, just replace RootViewController with the class name.
Related
I have a app that is locked to portrait in all views except one that is AllButUpsideDown. The approach i am using is to enable Portrait, Landscape Left and Landscape Right in the targets general settings menu. Then have subclasses of UINavigationController and UITabBarController that override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask and returns .Portrait.
Then in my view controller that needs to be able to be rotated I have also overridden func supportedInterfaceOrientations() -> UIInterfaceOrientationMask and returns .AllButUpsideDown. This works fine since this view controller is only presented as a modal i.e aViewController.presentViewController().
All of this work as expected on iOS9 on iOS8 however if i close the rotatable view controller while in landscape the UI will be scaled to landscape altho it will be displayed in portrait.
Anyone know what to do about this? Am I approaching this rotation thing wrong from the start? Any clean fixes? Workarounds? Hacks?
UPDATE
My problem originated from me using a custom transition to present and dismiss the view controller that could rotate. I tried to work around it for some time with bunch of different solutions. The closest I got to a solution was to use a separate UIWindow for my rotatable view controller, that worked except a issue with the carrier bar still being in the wrong orientation, and that was something I did not manage to solve.
The solution(not really a solution) I went with was to only use the custom transition in iOS9+ and on iOS8 use the default present transition.
I had the similar issue when navigation back from VC, that supports landscape to the one that is only portrait. I didn't find a clean workaround. These couple of lines are not recommended to use, but if you are desperate you can force your device orientation when you are about to dismiss.
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
I'm a little confused about the auto rotation methods in iOS. I'm using Swift for my app, which contains a tab bar controller and a nav bar controller.
The issue is I want all view controllers to be locked in Portrait mode except for one view controller which shows an image. I want this view controller to be able to be seen in both Portrait or in Landscape orientation based on how the user wants to view the image.
If I turn off the left/right rotation in the deployment info settings and call the shouldAutorotate() - return true method then the view controller with the image stays locked and won't rotate.
If I turn on the left/right rotation in deployment info settings and call the shouldAutorotate() - return false in the view controllers that I want locked then they still auto rotate.
-I feel like this shouldn't be as difficult as it is and can't find a solid answer on this. I'm a little newer to app development so any advice suggestions are appreciated.
Thanks in advance!
Have you set the supportedInterfaceOrientations?
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.AllButUpsideDown
}
I want to say in each view controller which orientation he supports and restrict it to them. This is what I tried:
Use a custom navigation controller like in one of my old posts, but that doesn't seem to work anymore. The solution is similar to iOS 6 - (BOOL)shouldAutorotate not getting called for navigation controllers pushed viewControllers
I also tried Setting Orientation in Xamarin iOS, but the app crashes on the RootViewController. I get a NullReferenceException here.
Furthermore I tried IOS 8 : Restricting orientation on a View Controller from the deleted blog from Shri Chakraborty (shrixamarin). Here the app also crashes if I use application:supportedInterfaceOrientationsForWindow:, because the RootViewController is null. The solution seems to be similar to How do I restrict orientation per view controller in iOS7 in a navigation controller hierarchy
None of the solutions seems to work. Or do I miss something important? How can I restrict the orientation that view controller A is landscape only, view controller B is portrait only and view controller C can be shown in all available orientations?
Overriding GetSupportedInterfaceOrientations() in your view controller should solve the problem:
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations () {
return UIInterfaceOrientationMask.LandscapeRight;
}
The orientations you return here will be intersected with the app's allowed orientations (project properties) or with what you allow in your app delegate. So be sure to specify all orientations there or the maximum set of orientations you want to support.
See here for documentation.
-(BOOL)shouldAutorotate {
return NO;
}
Above method works for one controller but when there are multiple viewControllers pushed on a stack.
I want a particular controller that should be displayed in portrait mode only.
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
}
I have used above method suggested on stack overflow for iOS 8 but it does not give desired result.
First, use -supportedInterfaceOrientations instead of -shouldAutorotate. -shouldAutorotate should only be used when you must disallow autorotation based on factors determined at runtime. You know your view controller will always only support portrait mode, there is no runtime decision here.
Next, your navigation controller's delegate must implement the -navigationControllerSupportedInterfaceOrientations: method to return the result of calling -supportedInterfaceOrientations on the view controller at the top of the navigation stack.
-(NSUInteger)navigationControllerSupportedInterfaceOrientations:(UINavigationController *)navigationController {
return navigationController.topViewController.supportedInterfaceOrientations;
}
An important caveat: A view controller pushed onto a navigation stack has no control over its initial interface orientation; that will always be the current interface orientation. What the above technique will do is prevent the interface from rotating to any orientation other than portrait while that view controller is displayed.
I am using SWRevealViewController (https://github.com/John-Lluch/SWRevealViewController) to handle switching between two controllers, a "front" and a "rear".
The front controller is a UINavigationController and the rear controller is just a plain UIViewController that displays a list of menu items. The front UINavigationController pushes an instance of a view controller named FrontViewController. The rear UIViewController is an instance of RearViewController. The instance of SWRevealViewController is set as the root view controller once it is configured with the front and rear controllers, the delegate property of the reveal controller is set to the app delegate itself.
In both FrontViewController and RearViewController I am overriding shouldAutorotate and returning NO as well as overriding supportedInterfaceOrientations and returning UIInterfaceOrientationMaskPortrait.
However the app auto rotates and goes into Landscape while these two views are displaying when I rotate the device.
supportedInterfaceOrientations seems to only be invoked in FrontViewController but the value is not honored and the device rotates into landscape orientation.
I can't simply set the entire app to Portrait either because I have other detail views that I do want to support Landscape (movie player, etc).
How can I get SWRevealViewController working so I can restrict the app to Portrait in certain child views of the controller?
I have also noticed that the presentation changes are not being honored as well. In RearViewController I am overriding prefersStatusBarHidden to return YES but this method is never invoked. Similarly in FrontViewController I am overriding preferredStatusBarStyle to return UIStatusBarStyleLightContent but this method is never called either.
I have UIViewControllerBasedStatusBarAppearance set to YES in my plist.
UPDATE:
I have tried to use PKRevealController as suggested in the comments but the behavior is exactly the same. Supported orientations and status bar styles are completely ignore. supportedInterfaceOrientations on FrontViewController is the only override invoked but the return value of UIInterfaceOrientationMaskPortrait is not honored.
UPDATE 2:
I'm thinking that this is simply a limitation of these controls and they pretty much expect the support orientations to be the same throughout the application. I did however try MFSideMenu (https://github.com/mikefrederick/MFSideMenu) and it seems to handle supported orientations in different child views exactly as you would expect it to. I still don't have the status bar visibility and styles working, unfortunately.
To achieve this while not avoiding SWRevealViewController: Inside of it there is implemented method supportedInterfaceOrientations, which needs to be edited. I have done this:
- (NSUInteger)supportedInterfaceOrientations
{
UINavigationController* frontNavigationController = (UINavigationController*)self.frontViewController;
if ([frontNavigationController.visibleViewController isKindOfClass:[VCgallery class]]) {
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationPortrait | UIInterfaceOrientationPortraitUpsideDown;
}
All my viewControllers are portrait only except those that are of class VCgallery, which can be both portrait and lanscape.
The solution ended up being to subclass PKRevealController and override supportedInterfaceOrientations, shouldAutorotate, preferredInterfaceOrientationForPresentation, prefersStatusBarHidden, and preferredStatusBarStyle.
While MFSideMenu did this for orientation it did not support status bar configurations. I also ran into a major bug with MFSideMenu that prevented me from using it in my project.
I made each method I override return a value from the appropriate controller depending on the circumstance. In the case orientation I return the value from self.frontViewController.topViewController (since I am using a UINavigation controller). Status bar style and visibility came from either self.frontViewController or self.leftViewController depending on the current state.
The same solution probably would have worked for SWRevealViewController as well but I preferred the API design of PKRevealController.
I figured subclassing would work from the beginning but I assumed that such a common scenario would be handled in the configuration of these controls.