Different interface orientation for UIViewControllers in UINavigationController stack - ios

I have a navigation controller with a rootviewcontroller with portrait orientation. Then i want to push a second viewcontroller to the stack with landscape orientation. Sadly i found no way to force the app to recheck supportedInterfaceOrientations. So the landscape viewcontroller is shown in protrait until the user rotates his device into landscape.
I prepared a test project: https://github.com/buechner/InterfaceOrientationTest
Is it even possible to automatically change the orientation within a navigationcontroller stack?

You can present a ViewController within LandscapeViewController to forcefully make it landscape
Use following line of the code in your viewDidLoad of LandscapeViewController
self.performSelector(#selector(LandscapeViewController.launchLandscapeScreen), withObject: nil, afterDelay:1)
add following method in your LandscapeViewController
func launchLandscapeScreen() -> Void{
let viewController = UIViewController()
self.presentViewController(viewController, animated: false, completion: nil)
self.dismissViewControllerAnimated(false, completion: nil)
}

It could be done but you'll have to make UIStoryboardSegue subclass
Make a subclass of UIStoryBoradSegue and override perform()
override func perform() {
let sourceVC = self.sourceViewController
let destonationVC = self.destinationViewController
sourceVC.navigationController!.presentViewController(destonationVC, animated: true, completion: nil)
}
In your NavigationControllerSubclass change shouldAutoRotate and supportedInterfaceOrientations
public override func shouldAutorotate() -> Bool {
return (self.topViewController?.shouldAutorotate())!
}
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return (self.topViewController?.supportedInterfaceOrientations())!
}
while connecting Segue from viewControllers select your segue subclass
4.add shouldAutorotate and supportedInterfaceOrientations methods in every class to
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Portrait // or landscape in case you want orientation to be landscape
}
As we're making custom segue, you also need to add back button.

Related

Preventing rotation in a specific view controller in iOS 11

I am trying to prevent rotation (lock it to say, portrait) in a specific VC that is
embedded in a navigation controller.
I am currently doing this:
To UINavigationController
extension UINavigationController {
public override func supportedInterfaceOrientations() -> Int {
return visibleViewController.supportedInterfaceOrientations()
}
}
In my VC:
class ViewController: UIViewController {
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}
}
However, I have issues when we go to another VC (embedded in another nav controller) is presented which supports both landscape and portrait. Suppose, the user rotates in the new screen to landscape. And clicks back to go to original screen. The app is now presented in landscape as opposed to portrait defined in its supportedInterfaceOrientations override. How do I prevent this erroneous behaviour?
I read in iOS 11, we should use viewWillTransition(to:with:) to handle rotation (and locking as well). In UIViewController documentation
“As of iOS 8, all rotation-related methods are deprecated. Instead,
rotations are treated as a change in the size of the view controller’s
view and are therefore reported using the viewWillTransition(to:with:)
method. When the interface orientation changes, UIKit calls this
method on the window’s root view controller. That view controller then
notifies its child view controllers, propagating the message
throughout the view controller hierarchy.”
Can you give directions on how to achieve it?
You can use this cool utility that I've been using.
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
/// OPTIONAL Added method to adjust lock and rotate to the desired orientation
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
You can also rotate your screen and at the same time, lock it to that orientation. I hope this helps!
You can use supportedInterfaceOrientationsFor
Define this in your AppDelegate
var restrictRotation = Bool()
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if restrictRotation {
return .portrait
}
else {
return .all
}
}
Put below code in your ViewController
func restrictRotation(_ restriction: Bool) {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.restrictRotation = restriction
}
call above function in your ViewController ViewwillAppear like this.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.restrictRotation(true) // TRUE MEANS ONLY PORTRAIT MODE
//OR
self.restrictRotation(false) // FALSE MEANS ROTATE IN ALL DIRECTIONS
}

How to keep controller in only one orientation?

I have two classes which subclass from UIViewController. PortraitViewController and LandscapeViewController.
These classes have their vars shouldAutorotate, supportedInterfaceOrientations and preferredInterfaceOrientationForPresentation with override implementation so they can stick to portrait or landscape orientation accordingly.
My application accepts Portrait and Landscape mode.
Now, I also have:
ViewController1 which subclasses from PortraitViewController
ViewController2 which subclasses from LandscapeViewController
When I show ViewController1 with a UINavigationController attached to it, it sticks with portrait as expected.
When I show ViewController2 with a UINavigationController attached to it, as a modal nav on top of ViewController1, it sticks with landscape as expected.
There's an extension for UINavigationController which also overrides the vars mentioned above, but it reads properties from the visibleController parameter.
But when I dismiss ViewController2, ViewController1 appears in landscape.
How can I make ViewController1 stick in portrait mode, regardless of what I show on top of it?
Note: Every time the device is in portrait mode.
EDIT:
Here's a demo project: https://www.dropbox.com/s/ishe88e1x81nzlp/DemoOrientationApp.zip?dl=0
The visibleViewController is either the top view controller of the navigation stack or a view controller presented from the navigation controller.
So, when you present the second navigation controller, the first navigation controller reads the second navigation controller property which passes back the landscape view controller's property.
What you need to use instead is topViewController. That way, the setting is limited to the view controller stack.
Okay, so:
Throw away your UINavigationControllerExtension.
Change your Info.plist so that we launch only into portrait:
(Delete items 1 and 2.)
In the app delegate, add this code, to allow us to rotate to landscape after launch:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .all
}
In ViewController, fix the code as follows:
class ViewController: PortraitViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return self.supportedInterfaceOrientations
}
#IBAction func showView(_ sender: Any) {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController2") as! ViewController2
let nav = UINavigationController(rootViewController: vc)
nav.delegate = vc
present(nav, animated: true, completion: nil)
}
}
In ViewController2, fix the code as follows:
class ViewController2: LandscapeViewController, UINavigationControllerDelegate {
func navigationControllerSupportedInterfaceOrientations(_ navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return self.supportedInterfaceOrientations
}
func navigationControllerPreferredInterfaceOrientationForPresentation(_ navigationController: UINavigationController) -> UIInterfaceOrientation {
return self.preferredInterfaceOrientationForPresentation
}
#IBAction func dismiss(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
The app now behaves as desired.

Prevent UIViewController from rotating when modal is rotating

My App is basically a portrait only app. So nothing is rotating. But not there is ONE exception. The user can add photos and when viewing those photos full-screen, this ViewController SHOULD be allowed to rotate.
So I thought that if my ViewController that is presenting has supportedInterfaceOrientations return .portrait and also shouldAutorotate return false, that this should be enough, to prevent that one from rotation?!?
Turns out, that when I rotate while having the full-screen image presented, the one underneath is rotated as well.
To summarize:
RootViewController should NEVER rotate
PresentedViewController can rotate, but his rotation should no rotate the RootViewController
Is there a way to achieve that?
Probably too late, but in case if somebody will be faced with the same issue, i would provide my solution.
Actually suppress rotation of underlying window is possible if set modalPresentationStyle = .fullScreen for presented controller, then if you take a look of "View UI Hierarchy" then you can notice that controller which represents controller in fullscreen will be removed from controllers hierarchy. But at the same time modalPresentationStyle = .overFullScreen keep everything as is, what causing rotating underlying controller even it set by default supported orientation to portrait, i.e. UIWindow who manages and routes system events over hierarchy respect settings of the toppest controller in case of modalPresentationStyle = .overFullScreen. So according to the facts, and if it is necessary to have e.g. custom presentation, i would suggest to use additional UIWindow which will be responsible for the presenting controller in fullscreen.
i have implemented test project for the solution: here
You can give an exception like in AppDelegate:
//auto rotate
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
//landscape for perticular view controller
let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController?.visibleViewController {
if activeController.isKind(of: VGVideoVC.self) {//Pass your VC here
// print("I have found my controller!")
return UIInterfaceOrientationMask.all;
}else{
return UIInterfaceOrientationMask.portrait;
}
}else{
return UIInterfaceOrientationMask.portrait;
}
}
And in the Rest of the VC where you want it to be forcefully portrait, you can use like this:
//MARK:- Screen Orientation
override var supportedInterfaceOrientations: UIInterfaceOrientationMask{
return .portrait
}
override var shouldAutorotate: Bool{
return true
}
Hope this helps.
I would say disable the orientation change for the complete app and listen to device orientation change in Photos view controller and update the UI of photosVC on device orientation change.
Something like this:
NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: Notification.Name("UIDeviceOrientationDidChangeNotification"), object: nil)
#objc func orientationChanged() {
if(UIDeviceOrientationIsLandscape(UIDevice.current.orientation)){
print("landscape")
}
if(UIDeviceOrientationIsPortrait(UIDevice.current.orientation)){
print("Portrait")
}
}
Be careful with the upside down and other orientations which you don't need.
Try this code below. I followed This tutorial and it works for me. What's going on is:
Step 1. Assuming inside General your Device Orientation is set to Portrait only:
Step 2. The code below that you add inside AppDelegate loops through the navigation controllers and then looks inside their top view controllers. If any of those vcs have a function with the name canRotate then that specific vc will change the device orientation from Step 1. by returning: return .allButUpsideDown
Add these 2 functions to the bottom of your AppDelegate:
// add this first function
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// if the navigationController's root vc has a function inside of it named canRotate
if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
if (rootViewController.responds(to: Selector(("canRotate")))) {
// Unlock landscape view orientations for this view controller
return .allButUpsideDown;
}
}
// Only allow portrait (standard behaviour). vcs that don't contain a function with the name "canRotate" can't rotate and stay in portrait only
return .portrait;
}
// add this second function
// loop through tabBarController or any navigationControllers
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
if (rootViewController == nil) { return nil }
if (rootViewController.isKind(of: UITabBarController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
} else if (rootViewController.isKind(of: UINavigationController.self)) {
return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
} else if (rootViewController.presentedViewController != nil) {
return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
}
return rootViewController
}
Step 3. Inside the modal vc you should add a function named: #objc func canRotate(){}. You don't have to call it anywhere or add anything inside it's curly braces. The code from Step 2 is looking for this function with the name canRotate. If the other vcs don't contain a function with that name then they can't rotate.
Inside the modal viewController that you want to rotate add the canRotate() function anywhere outside of viewDidLoad and inside viewWillDisappear add the code to set everything back to your regular Portrait only :
override func viewDidLoad() {
super.viewDidLoad()
}
#objc func canRotate(){}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// add this so once leaving this vc everything will go back to Portrait only
if (self.isMovingFromParentViewController) {
UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
}
}

iOS ViewController doesn't layout properly after orientation change

I'm writing an iOS application with some fixed (portrait) views and some orientation dependent views (portrait, lanscape left and landscape right).
The views are contained in a Custom Navigation Controller based on the following code:
class CustomNavigationViewController: UINavigationController {
override func supportedInterfaceOrientations() -> Int {
return self.topViewController.supportedInterfaceOrientations()
}
override func shouldAutorotate() -> Bool {
return self.topViewController.shouldAutorotate()
}
}
I'm implementing supportedInterfaceOrientations() and shouldAutorotate() inside nav child controllers.
Here is a Test Controller
class TestViewController: UIViewController {
var shouldRotate: Bool = false
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.shouldRotate = true
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init(rotation shouldRotate: Bool) {
self.init(nibName: "TestViewController", bundle: nil)
self.shouldRotate = shouldRotate
}
override func supportedInterfaceOrientations() -> Int {
if shouldRotate == false {
return UIInterfaceOrientation.Portrait.rawValue
} else {
return super.supportedInterfaceOrientations()
}
}
override func shouldAutorotate() -> Bool {
return shouldRotate
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
println("from: \(fromInterfaceOrientation.rawValue)")
}
#IBAction func buttonTapped(sender: AnyObject) {
let newvc = TestViewController(rotation: true)
self.navigationController?.pushViewController(newvc, animated: true)
}
}
The first controller is instantiated inside the AppDelegate with rotation: false. The other controllers are created with rotation: true and pushed after tapping the button.
Here is a screenshot of the view:
If I change orientation on the controllers after the first one (the rotable ones) I get the following result:
The controller xib uses autolayout and if I rotate the device before tapping the button it works as expected with the view filling the whole screen.
Also, if I tap the back button while the phone is in landscape the first view is locked in this state:
I'm deploying the app to iOS 8.
How can I make the first view portrait only and the other views correctly layed out after a rotation?
It's probably doesn't matter since it's only printing out a debug message, but don't forget that didRotateFromInterfaceOrientation(_:) was depreciated in iOS 8 with viewWillTransitionToSize(_:withTransitionCoordinator:) taking it's place for responsibilities of a change in view size (which includes rotation).
I think you're on the right track, but instead of overriding both shouldAutorotate() and supportedInterfaceOrientations() only override supportedInterfaceOrientations().
I just did a quick POC to see if it would work. I'm not setting a flag when creating the view controller (and using Swift 2) but you get the idea:
class RotatingViewController: UIViewController {
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.All
}
}
class FixedViewController: UIViewController {
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
}
class NavigationController: UINavigationController {
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return self.topViewController?.supportedInterfaceOrientations() ?? UIInterfaceOrientationMask.All
}
}
I think what's happening in your demo is that when you're transitioning to a view controller that is fixed, you're asking it to go into a portrait orientation (which it is) but then not allowing the screen to rotate back to portrait mode.
So.. just try deleting your 'shouldAutorotate()' in your CustomNavigationViewController and see if that works.
Problem is because shouldAutorotate return false.
Your FixedViewController should return shouldAutorotate value true, but supportedInterfaceOrientations should be for example only portrait.
Why do you need it?
When you come back (by using popViewController or dismissViewController) to your FixedViewController (which supports only portrait mode) from another RotateViewController in landscape mode, it should autorotate, to come back to portrait mode

how to lock portrait orientation for only main view using swift

I have created an application for iPhone, using swift, that is composed from many views embedded in a navigation controller. I would like to lock the main view to Portrait orientation and only a subview of a navigation controller locked in Landscape orientation.
Here is an example of what i mean:
UINavigationController
UiViewController1 (Locked in Portrait) Initial view controller with a button placed on the navigation bar that give to the user the possibility to access to a lists where can be selected other views
UIViewController2 (Locked in Landscape)
UiViewController3 (Portrait and Landscape)
UiViewController4 (Portrait and Landscape)
...
...
How Can i do that?
According to the Swift Apple Docs for supportedInterfaceOrientations:
Discussion
When the user changes the device orientation, the system calls this method on the root view controller or the topmost presented view controller that fills the window. If the view controller supports the new orientation, the window and view controller are rotated to the new orientation. This method is only called if the view controller's shouldAutorotate method returns true.
Your navigation controller should override shouldAutorotate and supportedInterfaceOrientations as shown below. I did this in a UINavigationController extension for ease:
extension UINavigationController {
public override func shouldAutorotate() -> Bool {
return true
}
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return (visibleViewController?.supportedInterfaceOrientations())!
}
}
And your main viewcontroller (portrait at all times), should have:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
Then, in your subviewcontrollers that you want to support portrait or landscape:
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.All
}
Edit: Updated for iOS 9 :-)
Also answered [here]
Things can get quite messy when you have a complicated view hierarchy, like having multiple navigation controllers and/or tab view controllers.
This implementation puts it on the individual view controllers to set when they would like to lock orientations, instead of relying on the App Delegate to find them by iterating through subviews or relying on inheritance.
Swift 3
In AppDelegate:
/// set orientations you want to be allowed in this property by default
var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientationLock
}
In some other global struct or helper class, here I created AppUtility:
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
/// OPTIONAL Added method to adjust lock and rotate to the desired orientation
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
Then in the desired ViewController you want to lock orientations:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
AppUtility.lockOrientation(.portrait)
// Or to rotate and lock
// AppUtility.lockOrientation(.portrait, andRotateTo: .portrait)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Don't forget to reset when view is being removed
AppUtility.lockOrientation(.all)
}
If your view is embedded in navigationcontroller in storyboard set the navigation controller delegate UINavigationControllerDelegate and add the following method
class ViewController: UIViewController, UINavigationControllerDelegate {
func navigationControllerSupportedInterfaceOrientations(navigationController: UINavigationController) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
}
Update: if you're having trouble to set orientation right after the app launches in iOS 10, try do it in ObjC instead of Swift, and with class MyNavigationController: MyNavigationControllerBase:
#implementation ABNavigationControllerBase
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
#end
Swift 3:
class MyNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
}
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .portrait
}
}
Same JasonJasonJason answer in Swift 4.2+ (It worked correctly with iOS 11)
1- Override shouldAutorotate and supportedInterfaceOrientations as shown below.
extension UINavigationController {
open override var shouldAutorotate: Bool {
return true
}
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return (visibleViewController?.supportedInterfaceOrientations)!
}
}
2- And your main viewcontroller (portrait at all times), should have:
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
3- Then, in your subviewcontrollers that you want to support portrait or landscape:
public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.all
}
The important think is to describe supported interface orientations for whole application in AppDelegate. For example to block all views to portrait just do this:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask{
return UIInterfaceOrientationMask.portrait
}
Lots of people are looking just for this answer by googling threw this question so I hope you can excuse me.
Works in Swift 3.0
In the main controller where you want portrait,
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.orientation = UIInterfaceOrientationMaskPortrait;
//Or self.orientation = UIInterfaceOrientationPortraitUpsideDown
}
and in subVC where you want Landscape use
self.orientation = UIInterfaceOrientationLandscapeLeft:
self.orientation = UIInterfaceOrientationLandscapeRight:
or you can override this method
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
This is how i would do it with Obj-c in iOS7, i think this code would work in iOS8 too
Edited for swift2.0 :
override func shouldAutorotate() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return [.Portrait, .PortraitUpsideDown]
}
Here is the Swift Update :-
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}
This is the syntax for Swift 3 (XCode 8.2 beta), where these methods where converted to properties:
extension UINavigationController {
override open var shouldAutorotate: Bool {
return false
}
}
class ViewController: UIViewController {
/*
...
*/
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
}
This requires two things
Informing the controller of its support for rotation.
Enforcing rotation and then handing over responsibility to a controller that knows its support for rotation.
Declare an extension on view controller that forces orientation to portrait.
extension UIViewController {
func forcePortrait() {
UIView.setAnimationsEnabled(false)
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
UIView.setAnimationsEnabled(true)
}
}
Any view controller that is locked to portrait could inherit traits.
class PortraitViewController: UIViewController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait }
override open var shouldAutorotate: Bool { return false }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
forcePortrait()
}
}
Any view controller that is capable of rotating between portrait and landscape can inherit those traits.
class LandscapeViewController: UIViewController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { return [.landscape, .portrait] }
override open var shouldAutorotate: Bool { return true }
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// if leaving for a portrait only screen, force portrait.
// forcePortrait()
}
}
If your landscape view controller is about to segue to a portrait locked screen. Be sure to lock the orientation just before leaving. Then rely on the portrait view controller to enforce its own lack of rotation.
Here is the working code to lock the orientation:
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
More information:
https://www.hackingwithswift.com/example-code/uikit/how-to-lock-a-view-controllers-orientation-using-supportedinterfaceorientations
You do have to apply this to your top view controller. However, you can do it in a clean/easy way by subclassing your top view controller and setting a variable within it that to references every time you make a call to:
shouldAutoRotate()
which is called when the device detects an orientation change and precedes the call to:
supportedInterfaceOrientations()//this is only called if shouldAutoRotate() returns true
For example, say my top view controller is a TabBarController:
class SomeSubclassTabBarViewController: UITabBarController { //This subclass allows us to pass along data to each of the tabBars
var dataArray = StoredValues()//also useful for passing info between tabs
var shouldRotate: Bool = false
override func shouldAutorotate() -> Bool { //allow the subviews accessing the tabBarController to set whether they should rotate or not
return self.shouldRotate
}
}
Then within the view which should have the ability to rotate the screen, set the viewWillAppear() and viewWillDisappear() like so:
override func viewWillAppear(animated: Bool) { //set the rotate capability to true
let sharedTabBarController = self.tabBarController as SomeSubclassTabBarViewController
sharedTabBarController.shouldRotate = true
}
override func viewWillDisappear(animated: Bool) {
let sharedTabBarController = self.tabBarController as SomeSubclassTabBarViewController
sharedTabBarController.shouldRotate = false
}
and just in case your app crashes while on this screen, it's probably a good idea to explicitly set the shouldRotate: Bool value on each of your views within the viewWillLoad() method.
In iOS8, if you want to lock some especific ViewController, create an extension of UINavigationController (in the case you use it):
extension UINavigationController {
public override func shouldAutorotate() -> Bool {
if visibleViewController is YourViewController {
return false
}
return true
}}
If your iOS application is in Landscape mode only and You want to use camera in landscape mode of application then please try for below solution.
Step 1:
In your appdelegate.m class
-(UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
NSString *captionVal = [TMUtils getValueInUserDefault:#"CAPTION"];
if ([captionVal isEqualToString:#"Camera"]) {
return UIInterfaceOrientationMaskPortrait;
}else{
return UIInterfaceOrientationMaskLandscapeRight;
}
}else{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
}
Here you can take shared preference value CAPTION as keyValue pair and store the value "Camera".
Step 2:
Now in your viewController.m class in camera button Action set shared preference value and open new ViewController which will be having camera functionality.
[TMUtils setValueInUserDefault:#"CAPTION" value:#"Camera"];
Step 3:
In Camera functionality viewController.m class set storyboard with UIImageView and back button.
Now in ViewDidLoad of camera functionality viewController.m set
- (void)viewDidLoad {
[super viewDidLoad];
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
NSLog(#"Error");
} else {
UIImagePickerController *pickerController = [[UIImagePickerController alloc] init];
pickerController.modalPresentationStyle = UIModalPresentationCurrentContext; //this will allow the picker to be presented in landscape
pickerController.delegate = self;
pickerController.allowsEditing = YES;
pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
[self presentViewController:pickerController animated:YES completion:nil];
}
}
Now in UIImagePickerController delegate method set image to UIImageView
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
self.cameraImage.image = chosenImage;
[picker dismissViewControllerAnimated:YES completion:NULL];
}
Now on camera back UIButton
- (IBAction)cameraBtnAction:(id)sender {
[TMUtils setValueInUserDefault:#"CAPTION" value:#"NOCamera"];
[self dismissViewControllerAnimated:YES completion:nil];
}
It will always check according to shared preference value in delegate class function for supportedInterfaceOrientationsForWindow in reference to that value it allow camera functionality ViewController to open camera in Portrait mode and rest it will again go back to Landscape mode, which will completely work fine.
When UIKit detects a change in device orientation, it uses the UIApplication object and the root view controller to determine whether the new orientation is allowed. If both objects agree that the new orientation is supported, then auto-rotation occurs. Otherwise, the orientation change is ignored.
By default, the UIApplication object sources its supported interface orientations from the values specified for the UISupportedInterfaceOrientations key in the applications' Information Property List. You can override this behavior by implementing the application:supportedInterfaceOrientationsForWindow: method in your application's delegate. The supported orientation values returned by this method only take effect after the application has finished launching. You can, therefore, use this method to support a different set of orientations after launch.
Allowing your app to rotate into portrait after launch.
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
source: https://developer.apple.com/

Resources