FirstViewController (Portrait only) push LandscapeViewController (Landscape only)
So I use UIDevice 'orientation'
How to lock orientation of one view controller to portrait mode only in Swift
But this method is also rotate FirstViewController
Can I keep FirstViewController in portrait?
FirstViewController set
shouldAutorotate false, supportedInterfaceOrientations only portrait
not worked.
FirstViewController
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override var shouldAutorotate: Bool {
return false
}
LandscapeViewController
override func viewDidLoad() {
super.viewDidLoad()
AppUtility.lockOrientation(.landscape, andRotateTo: .landscapeLeft)
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
override var shouldAutorotate: Bool {
return true
}
Related
The title may be confusing, so I will explain with an example:
There are 3 view controllers in the navigation stack: FirstViewController, SecondViewController and ThirdViewController.
FirstViewController supports landscape interface orientation only while SecondViewController and ThirdViewController support portrait only.
When FirstViewController shows up, SecondViewController will be pushed into the stack after 2 seconds, and we can see a rotation animation from landscape to portrait.
Then I continue to push a ThirdViewController instance and pop SecondViewController at the same time by assigning a view controller array to navigation controller.
The problem is in Step 4: For iOS 16, there is also a rotation animation for ThirdViewController, just like when SecondViewController is pushed into the stack.
class CustomNavigationController: UINavigationController {
override var shouldAutorotate: Bool {
return topViewController?.shouldAutorotate ?? false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return topViewController?.supportedInterfaceOrientations ?? .portrait
}
}
class FirstViewController: UIViewController {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeRight
}
override func viewDidLoad() {
super.viewDidLoad()
title = "First"
view.backgroundColor = .green
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let vc = SecondViewController()
self.navigationController?.pushViewController(vc, animated:true)
}
}
}
class SecondViewController: UIViewController {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Second"
view.backgroundColor = .yellow
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
let vc = ThirdViewController()
var viewControllers = self.navigationController?.viewControllers ?? []
viewControllers.removeLast()
viewControllers.append(vc)
// setting animated to false doesn't help
self.navigationController?.setViewControllers(viewControllers, animated: true)
}
}
}
class ThirdViewController: UIViewController {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Third"
view.backgroundColor = .red
}
}
Is there any way to solve this weird rotation animation on iOS 16?
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)!
}
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'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
}
}
}
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")