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.
Related
I have a simple app in which a portrait UINavigationController presents a landscape UINavigationController. Usually it works fine. Sometimes though, the landscape view controller appears in portrait orientation, but with landscape bounds.
Here's how it works: to restrict the orientation of the navigation controllers, I have a class, OrientableNavigationController that derives from UINavigationController and exposes the rotation-specific properties:
class OrientableNavigationController: UINavigationController {
private var _supportedInterfaceOrientations: UIInterfaceOrientationMask = .all
private var _shouldAutorotate: Bool = false
private var _preferredInterfaceOrientationForPresentation: UIInterfaceOrientation = .portrait
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
get { return _supportedInterfaceOrientations }
set { _supportedInterfaceOrientations = newValue }
}
override var shouldAutorotate: Bool {
get { return _shouldAutorotate }
set { _shouldAutorotate = newValue }
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
get { return _preferredInterfaceOrientationForPresentation }
set { _preferredInterfaceOrientationForPresentation = newValue }
}
}
I init one of these within the AppDelegate, give it a view controller and set it as the rootViewController of the window:
private var portraitNavigationController = OrientableNavigationController()
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
portraitNavigationController.supportedInterfaceOrientations = .portrait
portraitNavigationController.shouldAutorotate = true
portraitNavigationController.preferredInterfaceOrientationForPresentation = .portrait
let portraitViewController = PortraitViewController()
portraitViewController.delegate = self
portraitNavigationController.pushViewController(portraitViewController, animated: false)
window = UIWindow(frame: UIScreen.main.bounds)
window!.rootViewController = portraitNavigationController
window!.makeKeyAndVisible()
return true
}
It looks like this:
And that 'Present Landscape View Controller' button is hooked up to do this:
func portraitViewControllerButtonWasTouched(_ viewController: PortraitViewController) {
let landscapeNavigationController = OrientableNavigationController()
landscapeNavigationController.supportedInterfaceOrientations = .landscape
landscapeNavigationController.shouldAutorotate = true
landscapeNavigationController.preferredInterfaceOrientationForPresentation = .landscapeLeft
landscapeNavigationController.isNavigationBarHidden = true
let landscapeViewController = LandscapeViewController()
landscapeNavigationController.pushViewController(landscapeViewController, animated: false)
portraitNavigationController.present(landscapeNavigationController, animated: true)
}
Most of the time, this works fine. The landscape view controller is presented like so:
But sometimes, this happens:
This occurs in both simulator and device. Any ideas?
I have 2 View Controllers, VC1 and VC2.
VC1 currently presents VC2 modally.
VC1 only orientation should be portrait but VC2 can have all orientations.
The issue is when I am in VC2 and I rotate to landscape mode and then dismiss, VC1 is in landscape mode also! That should never happen!
NOTE: There is no Navigation Controller or UITabbarcontroller
I am adding my code bellow.
Appdelagate:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
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)
return .portrait;
}
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
}
Code in VC2:
override func viewDidLoad() {
super.viewDidLoad()
UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
}
func canRotate() -> Void {}
Link to where I went for help and found this code
Website where I found Code
Thanks so much for the help!
You need to follow the below steps to lock rotation for specific ViewControllers :-
Step 1: While creating your project, allow all the orientations. Do not select anything in below image.
Step 2: If you want VC1 to have only Portrait Orientation the implementation then add the below two functions in your ViewController Class
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.all //return the value as per the required orientation
}
override var shouldAutorotate: Bool {
return false
}
Step 3: If you wish VC2 to have all the orientation then do not add any code for it.
So the conclusion:-
In project setting, allow all the orientations for whole project. Restriction should be at ViewControllers level not at project level.
If you wish any VC to have all orientation then don't write any code.
If you wish any VC to have specific orientation then implement two functions above.
I've been stuck on this for a while now and can't figure out how I can override the "shouldAutoRotate" variable of TabBarController from the child (Navigation Controller ---> TableViewController)
So basically here's my setup
TabBarController ---> Navigation Controller ---> Main TableViewController ---> VocabularyDetail TableviewController
I know the below overrides in TabBarController will lock the rotation for all child views.
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
override var shouldAutorotate : Bool {
return false
}
However the challenge is that I want to selectively do this override depending on which view has been loaded into the navigation controller. If you look at the picture, the last controller is "Vocabulary Detail" which is the one that should change the "shouldAutorotate" variable to true.
iOS 10
Xcode 8.2
Swift 3
I finally figured it out :)
For those out there who are stuck at the same problem, here's the solution.
You need to write two extensions. One for the TabBarController and one for the NavigationController. Then in the extension of the Navigation controller you will need to override the values you're interested in by checking the view that's being loaded into the navigation.
UITabBarController extension will basically pass on the value from the child UINavigationController
extension UITabBarController {
override open var shouldAutorotate: Bool {
get {
if let visibleVC = selectedViewController {
return visibleVC.shouldAutorotate
}
return super.shouldAutorotate
}
}
override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{
get {
if let visibleVC = selectedViewController {
return visibleVC.preferredInterfaceOrientationForPresentation
}
return super.preferredInterfaceOrientationForPresentation
}
}
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask{
get {
if let visibleVC = selectedViewController {
return visibleVC.supportedInterfaceOrientations
}
return super.supportedInterfaceOrientations
}
}
}
UINavigation extension will check for the view being loaded and set the proper value for the overrides. Below will basically allow my screens to rotate, but to the orientation that I'm interested. All views other than Flashcard will stay in Portrait, while flashcard will only be in landscape.
extension UINavigationController {
override open var shouldAutorotate: Bool {
get {
if let visibleVC = visibleViewController {
return visibleVC.shouldAutorotate
}
return super.shouldAutorotate
}
}
override open var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation{
get {
if let visibleVC = visibleViewController {
if visibleVC.isKind(of: FlashCardController.classForCoder()) {
return UIInterfaceOrientation.landscapeLeft
} else {
return UIInterfaceOrientation.portrait
}
}
return super.preferredInterfaceOrientationForPresentation
}
}
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask{
get {
if let visibleVC = visibleViewController {
if visibleVC.isKind(of: FlashCardController.classForCoder()) {
return UIInterfaceOrientationMask.landscape
} else {
return UIInterfaceOrientationMask.portrait
}
}
return super.supportedInterfaceOrientations
}
}
}
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
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")