Disable auto rotation - ios

I am new to Xcode and Swift. (Xcode 7.2, Swift 2.0) My first project contains a navigation controller and 3 other view controllers: A, B and C. I do not want any views to auto rotate. I want A and B permanently in portrait and C permanently in landscape. In General -> Deployment Info -> Device Orientation I have checked Portrait and Landscape left. In each VC I have code like this in viewDidLoad.
let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
and the following method…
override func shouldAutorotate() -> Bool {
return false
}
Each view starts out in the desired orientation. However the darn thing keeps auto rotating.

You can't do what you're describing. Different view controllers at different depths in a navigation controller cannot have different fixed orientations. It isn't supported.
The only way to give a view controller a different fixed orientation from the previous view controller is as a presented view controller, not a view controller pushed onto a navigation controller's stack.
Also, this line:
UIDevice.currentDevice().setValue(value, forKey: "orientation")
would likely get your app rejected at the App Store. You are not allowed to set the device's orientation like this.

First way : If you don't want to rotate any VC in your project just uncheck the Device Orientation properties in your Targets window.
Second way: If you are using UINavigationController and if you want to call shouldAutorotate() function you should create a custom UINavigationController class and import the following code,
Like this, in your customNavigationController class,
override func shouldAutorotate() -> Bool {
return false
}
In this way you can change to another NavigationController's viewControllers to allow oriantations

Related

ios-to maintain screen orientation of a particular active ViewController after resuming from background state

For a graph view screen in my application the orientation has to be landscape mode, and for others screens the orientation is portrait mode.
The problem is when the graph view is on active screen and app goes in background.When app returns back to active state the orientation of graph changes to portrait mode which is undesirable.
Is there any way to retain the orientation of graph view?
Note:The graph view controller is not connected with any navigation controller and is called programmatically after a button click event.
Thanks in advance.
There is a way to lock the orientation. Probably it will solve your problem.
I found this answer to make things easier and not writing it one more time here.
Solution: link.
Hope it helps!
Here are some prerequisites.
First we choose the required orientations for the application:
Second we need to add some parameters in info.plist,check that Supported Interface Orientations contains the orientations selected in first step.
Now coming to the coding part:
In Appdelegate class, add this code to following function: func applicationDidBecomeActive(_ application: UIApplication)
//case 1: for view controllers not part UINavigationController in your.storyboard
if(self.window?.rootViewController?.presentedViewController is GraphViewController){
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
//or
//case 2: for view controllers which are part UINavigationController in your.storyboard
let currentVC = self.window?.rootViewController as? UINavigationController
if(currentVC?.viewControllers.last is GraphViewController){
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
That's it, now after running the application, if the app is sent to background (while GraphViewcontroller is on active screen) by pressing the home button and then returning to app by selection from app tray, the GraphViewcontroller will retain the orientation.

Is there a way to have a single view not rotate?

I am trying to create a view that doesn't rotate with device orientation. I want just this one view to not rotate because I have a UIToolbar that I don't want to rotate. Is this possible? If so, how do I implement it? I am using Swift. Also, is it possible to rotate the UIBarButtonItems with device orientation on the UIToolbar? If so, how do I implement that? Just to restate: I want the view and toolbar within it to not rotate with orientation; I want the buttons on the toolbar to rotate with orientation. Thanks.
Per the View Controller Programing Guide
If you want to temporarily disable automatic rotation, avoid manipulating the orientation masks to do this. Instead, override the shouldAutorotate variable on the initial view controller. This method is called before performing any autorotation. If it returns NO, then the rotation is suppressed.
So you need to subclass 'UINavigationController', implement shouldAutorotate and use your navigation controller class in your storyboard.
Swift 3
override var shouldAutorotate: Bool {
let currentViewController = self.topViewController
return !currentViewController.isKind(of: DetailViewController.classForCoder())
}
Why not just rotate the view in question -90 degrees or +90 degrees as required? I'll happily provide example code if you'd like.
Edit:
I was suggesting something like this (but look at the caveat after the code):
let degrees = 90.0 // or -90, 180 depending on phone's movement.
let rotatedView: UIView = <yourOriginalView>
rotatedView.transform = CGAffineTransform = CGAffineTransform(rotationAngle: degrees * CGFloat(M_PI / 180))
In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame. -- So this might not work for your purposes if you're using autolayout.... And I admit this is a kludge. 😉🙄😉
I see two possibilities, and prefer the second because it doesn't rotate your views twice. I will use the term static view for the single view that you don't want to rotate (= the UIToolbar).
Possibility 1
Subscribe to device orientation changes. Then, when the orientation changes, and the UI rotates, rotate the static view in the opposite direction.
For example when you get a notification that the device is rotated 90° clockwise:
The whole user interface (including the static view) will rotate 90° counterclockwise automatically
Rotate the static view 90° clockwise, to cancel the automatic rotation behaviour.
Possibility 2
Disable the automatic rotation behaviour and then manually rotate all views except the one you don't want to rotate. To rotate multiple views at once: put them in a custom view and rotate that custom view. Here is a step-by-step guide:
Disable automatic rotation in your view controller. Do this by overriding the ViewController's shouldAutoRotate variable.
In interface builder: put everything except the static view in a custom UIView.
Observe device orientation changes and rotate the statusbar and your custom view (you created in step 2) when the device's orientation changes.
An example implementation in swift can be found at: GitHub/Dev1an/Static iOS toolbar
Rotating views
For the question "how to rotate views" have a look at: https://stackoverflow.com/a/28717635/2616297
Straightforward. You should simply override the shouldRotate variable and set it to false for the VC you wish to prevent rotation in.
UPDATED - Swift 3
override var shouldAutorotate: Bool {
return false
}
I used the information from all of the answers here, as well as from this post overriding shouldAutorotate not working in Swift 3, to find the answer to my question. I created a MainNavigationController class within my MainViewController.swift file and then overrided shouldAutorotate like so:
import UIKit
class MainNavigationController: UINavigationController {
override var shouldAutorotate: Bool {
return false }
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
The last step was to go into my Main.storyboard file, click on the navigation controller for the view, and change its class to MainNavigationController. Once I did that, it worked perfectly. Thanks to all the people who answered this question.

Clarifications on managing orientations in UIViewController

I have a configuration like the following:
UINavigationController
-> ViewController
where ViewController is the root view controller for the navigation controller.
ViewController can present modally another view controller, ModalViewController, like the following.
self.presentViewController(modalViewController, animated: true, completion: nil)
Within ModalViewController I override the following method. The modal view controller in fact can be presented only in Landscape mode.
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return .Landscape
}
I then override ViewController's method for responding to orientation changes.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
print("Size is \(size)")
}
What I noticed is that if the modal view controller is presented, the print in ViewController is logged only if ModalViewController is in Landscape mode, while it is no logged in Portrait. In other words, while I rotate the device, ModalViewController should be able to display Landscape orientations only. Under the hood, even if ViewController is not visible (and the device is in Portrait), this controller should be able to respond to size changes. This is not the case since I cannot see the print log.
Use case:
if ModalViewController is not visible this is the print log
Size is (1024.0, 768.0)
Size is (768.0, 1024.0)
Size is (1024.0, 768.0)
Size is (768.0, 1024.0)
when ModalViewController is presented modally
Size is (1024.0, 768.0)
Size is (1024.0, 768.0)
Is this one the correct behaviour? My goal is to respond to orientation changes for the ViewController even if (when ModalViewController is opened) the device is in Portrait mode. Any clue?
Edit
Based on matt comment.
If ViewController is not the frontmost view controller it has no business "responding to orientation changes". You've merely stumbled across an implementation detail or edge case in which you should have no interest.
ViewController is a complex controller that acts as a parent view controller. It has two children: PortraitViewController and LandscapeViewController. These controllers are swapped in viewWillTransitionToSize method. Whenever ModalViewController is not visible (not presented) the swapping works in the correct manner. On the contrary, when ModalViewController is presented, the swapping runs just for Landscape mode (see logs above).
It sounds to me as if you're just trying to do something at the wrong time. When the modal view controller is dismissed and your view controller's view is about to reappear, you'll get plenty of warning (including appear and layout events) and you can do whatever needs doing, based on the current orientation, before your view becomes visible to the user.

iOS - unexpected rotation back to portrait on segue

first time posting, so forgive me if I'm not giving the right info in my question...
I'm creating an app (in swift), and I want to support all screen orientations for all screens (and have set in Xcode general tab accordingly). There's a login / launch screen, and then the root controller for the rest of the app is a UINavigationController.
My problem - The screen keeps rotating back to portrait on any segue, even though the device is in landscape orientation, and the screens all support landscape! Is this the standard behaviour on a segue? And if so, can I prevent it somehow?
To be clear - I just want the screen rotation to continue to reflect the device orientation following a segue - and all screens currently support all orientations.
I've tried setting shouldAutorotate to return false for a given screen / view controller, and extended UINavigationController to refer to the visible view controller's shouldautorotate() function, as follows:
extension UINavigationController {
public override func shouldAutorotate() -> Bool {
return visibleViewController!.shouldAutorotate()
}
}
This prevents the rotation away from the (landscape) device orientation on segue, but of course then if the user rotates back to portrait the screen remains landscape...
It seems like this should be really straightforward, but I couldn't find any info or other questions on it, just questions about restricting allowed orientations (I just want the screen orientation to reflect the device orientation at all times)...
One thought - is there a way to detect whether the shouldAutorotate function is being called following a segue? And return false in this instance, but true otherwise?
Any help would be gratefully received!
Thanks
Dan
Add these 2 methods into your view controller:
override func shouldAutorotate() -> Bool {
return false
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}

Force iOS view to not rotate, while still allowing child to rotate

I have a view controller with a child view controller.
tab bar controller
|
|
nav controller
|
|
UIPageViewController (should rotate)
|
|
A (Video Player) (shouldn't rotate)
|
|
B (Controls overlay) (should rotate)
A should be forced to stay portrait at all times, but B should be allowed to rotate freely.
I know shouldAutorotate applies to any view controllers and its children, but is there any way to get around this? It seems like I could use shouldAutorotateToInterfaceOrientation, but this is blocked in iOS 8.
I'd like to keep a video player static (so horizontal videos are always horizontal regardless of device orientation), while the controls layer subview overlay is allowed to freely rotate.
I'm using Swift.
I had this exact problem, and found out quickly there's a lot of bad advice floating around about autorotation, especially because iOS 8 handles it differently than previous versions.
First of all, you don't want to apply a counterrotation manually or subscribe to UIDevice orientation changes. Doing a counterrotation will still result in an unsightly animation, and device orientation isn't always the same as interface orientation. Ideally you want the camera preview to stay truly frozen, and your app UI to match the status bar orientation and size as they change, exactly like the native Camera app.
During an orientation change in iOS 8, the window itself rotates rather than the view(s) it contains. You can add the views of multiple view controllers to a single UIWindow, but only the rootViewController will get an opportunity to respond via shouldAutorotate(). Even though you make the rotation decision at the view controller level, it's the parent window that actually rotates, thus rotating all of its subviews (including ones from other view controllers).
The solution is two UIWindow stacked on top of each other, each rotating (or not) with its own root view controller. Most apps only have one, but there's no reason you can't have two and overlay them just like any other UIView subclass.
Here's a working proof-of-concept, which I've also put on GitHub here. Your particular case is a little more complicated because you have a stack of containing view controllers, but the basic idea is the same. I'll touch on some specific points below.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var cameraWindow: UIWindow!
var interfaceWindow: UIWindow!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
let screenBounds = UIScreen.mainScreen().bounds
let inset: CGFloat = fabs(screenBounds.width - screenBounds.height)
cameraWindow = UIWindow(frame: screenBounds)
cameraWindow.rootViewController = CameraViewController()
cameraWindow.backgroundColor = UIColor.blackColor()
cameraWindow.hidden = false
interfaceWindow = UIWindow(frame: CGRectInset(screenBounds, -inset, -inset))
interfaceWindow.rootViewController = InterfaceViewController()
interfaceWindow.backgroundColor = UIColor.clearColor()
interfaceWindow.opaque = false
interfaceWindow.makeKeyAndVisible()
return true
}
}
Setting a negative inset on interfaceWindow makes it slightly larger than the screen bounds, effectively hiding the black rectangular mask you'd see otherwise. Normally you wouldn't notice because the mask rotates with the window, but since the camera window is fixed the mask becomes visible in the corners during rotation.
class CameraViewController: UIViewController {
override func shouldAutorotate() -> Bool {
return false
}
}
Exactly what you'd expect here, just add your own setup for AVCapturePreviewLayer.
class InterfaceViewController: UIViewController {
var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
contentView = UIView(frame: CGRectZero)
contentView.backgroundColor = UIColor.clearColor()
contentView.opaque = false
view.backgroundColor = UIColor.clearColor()
view.opaque = false
view.addSubview(contentView)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
let screenBounds = UIScreen.mainScreen().bounds
let offset: CGFloat = fabs(screenBounds.width - screenBounds.height)
view.frame = CGRectOffset(view.bounds, offset, offset)
contentView.frame = view.bounds
}
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
override func shouldAutorotate() -> Bool {
return true
}
}
The last trick is undoing the negative inset we applied to the window, which we achieve by offsetting view the same amount and treating contentView as the main view.
For your app, interfaceWindow.rootViewController would be your tab bar controller, which in turn contains a navigation controller, etc. All of these views need to be transparent when your camera controller appears so the camera window can show through beneath it. For performance reasons you might consider leaving them opaque and only setting everything to transparent when the camera is actually in use, and set the camera window to hidden when it's not (while also shutting down the capture session).
Sorry to post a novel; I haven't seen this addressed anywhere else and it took me a while to figure out, hopefully it helps you and anyone else who's trying to get the same behavior. Even Apple's AVCam sample app doesn't handle it quite right.
The example repo I posted also includes a version with the camera already set up. Good luck!
You can try this -
Objective -C code if you have its alternative in swift:
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if ()//Place your condition here like if A is visible
{
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAll;
}
You can subscribe to rotation change notifications, and manually set the rotation transform matrix on the subview you want to rotate.
I'm not sure, but I think you could create an own class for your subview and override the shouldAutorotate method etc. That way it should override the shouldAutorotate from the parent-viewcontroller.
Short answer: No, all visible controllers and views rotate (or don't rotate) together.
Long answer:
First, you must implement autorotate decision functions in the root controller; that may mean making a nav controller subclass.
You can hack your desired behavior by having the parent view autorotate -- but have it manually rotate itself back to appear un-rotated.
Or, you can NOT autorotate, but listen for notifications that the physical device rotated, and manually rotate whatever views you want to, eg: Replicate camera app rotation to landscape IOS 6 iPhone
Also see, fyi:
How to force a UIViewController to Portrait orientation in iOS 6
shouldAutoRotate Method Not Called in iOS6
iOS6: supportedInterfaceOrientations not working (is invoked but the interface still rotates)
How to implement UIViewController rotation in response to orientation changes?
The simplest, most straight-forward answer to this question is to look at Apple's AVCam sample code. The key parts for me were that it:
Uses a view whose layerClass is AVCaptureVideoPreviewLayer.
Sets the videoOrientation of the AVCaptureVideoPreviewLayer's connection to match the application's statusBarOrientation when the view is presented, essentially viewWillAppear(_:).
Sets the videoOrientation to match UIDevice.currentDevice().orientation in viewWillTransitionToSize(_:withTransitionCoordinator:).
Enables autorotation and supports all interface orientations.
I implemented the background window approach described by jstn and it worked fairly well, but the reality is that it is much more complicated than is necessary. AVCam works great and has relatively simple approach.

Resources