iOS - unexpected rotation back to portrait on segue - ios

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
}

Related

Swift manually rotate view controller

So I'm working on an iOS 10 app using Swift 3 and Xcode 8.3.3.
In my app I have to take successively 8 pictures with indicators on the camera.
So I'm using AVCaptureVideoPreviewLayer for the custom camera and I need to set the view displaying the camera on landscape mode.
I did it using this extension that I found on stackoverflow while searching for the problem.
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")
}
}
and in the viewDidLoad() of the customCameraView, I'm setting the locking the orientation to .landscapeLeft :
AppUtility.lockOrientation(.landscapeLeft)
before leaving the view I'm setting this in my viewWillDissapear method in order to unlock the orientation:
AppUtility.lockOrientation(.all)
The problem is that landscape mode in the customCameraView works only when the auto-rotation of the device is enabled (not locked) and when I get back to the previous view controller, it is initially displayed in landscapeLeft so I have to turn the device to put it portrait mode. this view is locked in portrait method using the AppUtility extension.
So I thought about always activate the auto-rotation by overriding the shouldAutoRotate var but it didn't work when the device auto-rotation is locked.
Then I thought about making sure that the auto-rotation is enabled before opening the camera but 100 of people on stackoverflow are saying that this is not possible.
So the perfect solution for me would be to always make the customCameraView in landscapeLeft mode and the previous view in portrait no matter if the rotate is activated or not.
I'm struggling on this bug for days now and a little help would be great.
iOS encourages developers to support both portrait and landscape, so it can be difficult to restrict one part of the app to be landscape while the other is portrait.
One option that works pretty well is to restrict the entire app to portrait mode in the project settings, and apply a rotation transform to any view controllers that need to be landscape. This way, the view will look and behave like it is landscape, and you have full control over the rotation of your views.
Can you apply below code on the required customCameraView Controller :
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.landscapeLeft
}

Disable auto rotation

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

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.

Force first screen of ios application to be portrait but other screens landscape ios 6+

I used this code for forcing my Home screen (first screen of my application) be portrait while other screens remain supporting all orientations:
public class RltNavigationController : UINavigationController
{
public RltNavigationController () : base ()
{
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations ()
{
if(this.TopViewController is HomeScreen )
return UIInterfaceOrientationMask.Portrait ;
else
return UIInterfaceOrientationMask.AllButUpsideDown ;
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
// Return true for supported orientations
if(this.TopViewController is HomeScreen )
return (toInterfaceOrientation == UIInterfaceOrientation.Portrait );
else
return (toInterfaceOrientation != UIInterfaceOrientation.PortraitUpsideDown) ;
}
}
Now, suppose that the device is on the landscape orientation at home screen (Device is lanscape but screen just show portrait). Now if user go to other views, other views now show portrait while it should show landscape. What solution I can choose in order to load second views with theirs actual rotation?
EDIT
Thanks for all answers, Just notice that already the problem is not that I can not force the screen to be portrait. For understanding the problem please follow the scenario:
-> First screen forced to be portrait.
-> Device is landscape right and I'm in home screen(so home screen show portrait)
-> Now I switch to another screen that support all orientation
-> at another screen because the parent screen was portrait it show portrait (while because device is landscape it should show landscape)
You can also directly select from the XIB a particular viewController be Landscape or Portrait and the same loads.
You can not explicitly say, viewController be landscape and the view will be landscape. The way it works is, you ask the controller that is controller the screen, this may be a navigation controller, tab view controller, a modal, how they want to be able to rotate. If you have a navigation controller then all viewController will only have the rotation of your navigation controller.
There were a few tricks like subclassing the navigation controller and over the should auto rotate method, call [self.visibleViewController shouldAutoRotate]; which works for making screens rotate and not rotate, but if you have only 1 screen that supports all orientations and all the others do not, then you have a pushing/popping error where if you push or pop while in that different orientation the next viewController will be in that orientation.
Since you can't directly tell the rootViewController to explicitly rotate, the next best solutions are,
A: Use QuartzCore to manually rotate the view yourself
B: have a separate xib file for each orientation so when you rotate to landscape you see the landscape viewController and vice versa
The easiest way to do this is to create a custom navigation controller (subclass of UINavigationController) that inherits its rotation based on the currently visible view controller
Inside your custom navigation controller:
-(NSUInteger) supportedInterfaceOrientations {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
-(BOOL) shouldAutorotate {
return self.topViewController.shouldAutorotate;
}
Then inside any of the view controllers within that, add these methods:
-(BOOL)shouldAutorotate{
return NO;
}
-(NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
That's for a view you DON'T want to autorotate. Modify that accordingly for views you do want to rotate.
Make sure your project settings have rotation in the orientations you want enabled, and make sure to use your custom navigation controller instead of the regular one for any view hierarchies that contain multiple possible rotations.
Note that you may run into problems if a view that is rotated is popped and the previous view is not rotatable. Off the top of my head, I'm not sure whether this will work properly.

Monotouch - View created in landscape orientation is displayed in portrait orientation

I've created a view controller and set the orientation of the view to Landscape in XCode 4.2 (Interface Builder). However, when I add the view, the view is displayed in Portrait orientation. I've overriden ShouldAutorotateToInterfaceOrientation in all view controllers to return true, and I've attempted to rotate the view manually using the following code:
this.View.Transform.Rotate (3.14f * .5f);
I have also tried to set the frame to a landscape frame (i.e. 480 X 320), though the frame of the view is already set correctly.
Just to clarify, the vast majority of my views are in Portrait. I would like to load the landscape view in a landscape orientation, irrespective of what orientation the device is actually in. Is this possible? If so, what am I missing?
Thanks in advance for any assistance on this matter!
UPDATE: I'm noticing that ShouldAutorotateToInterfaceOrientation is only called once when the view is loaded. It is not called when the device is rotated. Is this normal behavior?
ShouldAutorotateToInterfaceOrientation is called every time the device orientation changes.
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
Console.Out.WriteLine(toInterfaceOrientation.ToString());
// Return true for supported orientations
// This particular screen is landscape only!
return (toInterfaceOrientation == UIInterfaceOrientation.LandscapeLeft || toInterfaceOrientation == UIInterfaceOrientation.LandscapeRight);
}
this code will only allow the view to orient itself in either landscapeleft or landscaperight mode, and writes the new device orientation to the console every time.
when you load the view, and push it onto (for instance) a UINavigationController however, it's still in portrait mode (and ShouldAutorotateToInterfaceOrientation is not called.)
From Objective-C, it's fairly easy to force a device orientation, but in MonoTouch, there appears to be no direct mapping.
the solution ;
Send a message to the objective-c runtime, specifying which orientation we want.
you can easily do this by adding the following to a view controller:
Selector orientationSetter;
private void SetOrientation (UIInterfaceOrientation toOrientation)
{
if (orientationSetter == null)
{
orientationSetter = new Selector ("setOrientation:");
}
Messaging.void_objc_msgSend_int (UIDevice.CurrentDevice.Handle,
orientationSetter.Handle, (int)toOrientation);
}
you can now manually change the orientation of the entire device.
What's more, is that if you use a UINavigationController, the orientation will return back to normal once this view is popped off the stack.
I think that for the Landscape view that you always want to display in Landscape Orientation, you need to return False within ShouldAutorotateToInterfaceOrientation or remove the override altogether. This will prevent the view from auto-rotating when the device is in portrait mode.

Resources