Rotate presented view controller but not presenting view controller - ios

I want to be able to rotate a modally presented view controller, but not rotate the view controller that presented it. I am controlling orientation changes in my app like this:
In my AppDelegate:
var orientationLock: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return orientationLock
}
In my OrientationManager:
struct OrientationManager {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
In my ModallyPresentedViewController:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
OrientationManager.lockOrientation(.all)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
OrientationManager.lockOrientation(.portrait, andRotateTo: .portrait)
}
Currently this works, but it does rotate the modal view below it, which is why I have to call OrientationManager.lockOrientation(.portrait, andRotateTo: .portrait) when the view disappears. When the view disappears the presenting view controller is still rotated for a few seconds before it moves back to being in portrait mode. How can I prevent the presenting view controller from rotating? Thanks!
(Side note, does anyone know if UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation") is a private API?)

Related

handle orientation with multiple stack of viewcontrollers

Some quick issues, i have 5 VC on stack. when i am in my second VC, then if i rotate to landscape and when i move to 1st VC my layout is totally collapsed.
in my second VC i added viewWillTransition and i am invalidateLayoutof my table view. And if i add tableview.reloaddata() in my first screen viewWillAppear .Then it's fine.
But consider if i have 5 VC on stack i can not write code in all VC. Is there any better way to handle it.
In your app delegate add this....
var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientationLock
}
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
Used this from any view controller like this...
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)

Portrait by default, How can some special screens auto rotated or force some screens to landscape?

In my app, the orientation is portrait by default, but in some special screens I want the viewcontroller to be auto rotated or to force the orientation to landscape.
in AppDelegate.swift :
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// portrait by default
var interfaceOrientations:UIInterfaceOrientationMask = .portrait {
didSet{
// force to portrait
if interfaceOrientations == .portrait {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue,
forKey: "orientation")
}
// force to landscape
else if !interfaceOrientations.contains(.portrait){
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue,
forKey: "orientation")
}
}
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return interfaceOrientations
}
in only portrait vc:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
appDelegate.interfaceOrientations = .portrait
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
appDelegate.interfaceOrientations = .portrait
}
in auto rotated vc:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
appDelegate.interfaceOrientations = .allButUpsideDown
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
appDelegate.interfaceOrientations = .portrait
}
in only landscape vc:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
appDelegate.interfaceOrientations = [.landscapeLeft, .landscapeRight]
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
appDelegate.interfaceOrientations = .portrait
}
but it still has bugs...Help me
You should force the rotation with:
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
You also have to enable autoRotation
override var shouldAutorotate: Bool{
return true
}
And of course, in your project settings you have to enable both portrait and landscapes modes.
You can custom your Portrait View Controller and Landscape View Controller. LandscapeViewController like this:
```
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeRight
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeRight
}
```
Some points need to notice: UIViewController can't decide to rotate automatically, you can use UINavigationController to fetch the value of rotation property:
override var shouldAutorotate: Bool {
return (self.topViewController?.shouldAutorotate)!
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return (self.topViewController?.supportedInterfaceOrientations)!
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return (self.topViewController?.preferredInterfaceOrientationForPresentation)!
}

AVPlayer in fullscreen doesn't rotate

In our project we have blocked/unblocked the orientations of a view following this post and inserting the code below. Everything works great.
AppDelegate.swift
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = self.topViewControllerWithRootViewController(window?.rootViewController) {
if (rootViewController.respondsToSelector(Selector("canRotate"))) {
// Unlock landscape view orientations for this view controller
return .AllButUpsideDown;
}
}
// Only allow portrait (standard behaviour)
return .Portrait;
}
private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
if (rootViewController == nil) { return nil }
if (rootViewController.isKindOfClass(UITabBarController)) {
return topViewControllerWithRootViewController((rootViewController as! UITabBarController).selectedViewController)
} else if (rootViewController.isKindOfClass(UINavigationController)) {
return topViewControllerWithRootViewController((rootViewController as! UINavigationController).visibleViewController)
} else if (rootViewController.presentedViewController != nil) {
return topViewControllerWithRootViewController(rootViewController.presentedViewController)
}
return rootViewController
}
...
ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillDisappear(animated : Bool) {
super.viewWillDisappear(animated)
if (self.isMovingFromParentViewController()) {
UIDevice.currentDevice().setValue(Int(UIInterfaceOrientation.Portrait.rawValue), forKey: "orientation")
}
}
func canRotate() -> Void {}
}
The app is portrait-only, except a view where we have enable all the orientations. In this view there are also a container with an AVPlayer and the problem is that I don't know how to enable all orientations in this AVPlayerViewController also. The app allows all orientations in the view that contains the player, but once I have put the player in fullscreen this one doesn't rotate. I have tried using the code previously mentioned in the AVPlayerViewController of the AVPlayer in the container, but the player doesn't rotate.

Changing orientations programmatically does not work fine

My whole application is in portrait mode. I just want to use one view controller in landscape mode (left).
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask
{
if let _navigationController = window?.rootViewController as? UINavigationController {
if _navigationController.topViewController is FullScreenPlayerVC {
return UIInterfaceOrientationMask.LandscapeLeft
}
}
return UIInterfaceOrientationMask.Portrait
}
This is my controller A
override func shouldAutorotate() -> Bool
{
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask
{
return UIInterfaceOrientationMask.Portrait
}
Now i push Controller B. This is my controller B
override func viewDidAppear(animated: Bool)
{
super.viewDidAppear(animated)
let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
override func shouldAutorotate() -> Bool
{
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask
{
return UIInterfaceOrientationMask.Landscape
}
It works as per my requirement when i push controller B while holding my device in portrait mode but if i am holding my phone in landscape left already.
It does not perform desired action. Searched a lot about it but not able to find the solution yet.
I have tried many solutions but nothings working fine.
This is a generic solution for your problem and others related.
1. Create auxiliar class UIHelper and put on the following methods:
/**This method returns top view controller in application */
class func topViewController() -> UIViewController?
{
let helper = UIHelper()
return helper.topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController)
}
/**This is a recursive method to select the top View Controller in a app, either with TabBarController or not */
private func topViewControllerWithRootViewController(rootViewController:UIViewController?) -> UIViewController?
{
if(rootViewController != nil)
{
// UITabBarController
if let tabBarController = rootViewController as? UITabBarController,
let selectedViewController = tabBarController.selectedViewController {
return self.topViewControllerWithRootViewController(rootViewController: selectedViewController)
}
// UINavigationController
if let navigationController = rootViewController as? UINavigationController ,let visibleViewController = navigationController.visibleViewController {
return self.topViewControllerWithRootViewController(rootViewController: visibleViewController)
}
if ((rootViewController!.presentedViewController) != nil) {
let presentedViewController = rootViewController!.presentedViewController;
return self.topViewControllerWithRootViewController(rootViewController: presentedViewController!);
}else
{
return rootViewController
}
}
return nil
}
2. Create a Protocol with your desire behavior, for your specific case will be landscape.
protocol orientationIsOnlyLandscape {}
Nota: If you want, add it in the top of UIHelper Class.
3. Extend your View Controller
In your case:
class B_ViewController: UIViewController,orientationIsOnlyLandscape {
....
}
4. In app delegate class add this method:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
let presentedViewController = UIHelper.topViewController()
if presentedViewController is orientationIsOnlyLandscape {
return .landscape
}
return .portrait
}
Final Notes:
If you that more class are in landscape mode, just extend that
protocol.
If you want others behaviors from view controllers, create other protocols and follow the same structure.
This example solves the problem with orientations changes after push
view controllers

Disable rotation for item in tabBarController

I have a tabBarController which has 4 items. One of them is a camera (a barcode scanner) which I implemented with AVCaptureSession. So, if you tab the tab "scanner" will automatically show you a camera screen.
The problem is that I can't disable autorotate of individual items of the tabBarController. So, the screen of the camera rotates when you rotate the device and is very weird.
I tried:
override func shouldAutorotate() -> Bool {
return false
}
and
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Portrait
}
but nothing works.
In your AppDelegate add the following
var shouldSupportAllOrientation = false
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if (shouldSupportAllOrientation == true){
return UIInterfaceOrientationMask.All
}
return UIInterfaceOrientationMask.Portrait
}
Then go to each view and add the following in viewWillAppear
let appdelegate = UIApplication.sharedApplication().delegate as! AppDelegate
// false = only portrait
// true = all orientations
appdelegate.shouldSupportAllOrientation = false
Update
To lock the screen when you go from landscape to portrait, just add this code in viewWillAppear.
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")

Resources