Force iOS viewing to not spin, but letting the child spin

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 must be forced to constantly maintain a portrait, but B must be able to rotate freely.

I know that shouldAutorotate applies to any view controllers and their children, but is there a way around this? It looks like I could use shouldAutorotateToInterfaceOrientation , but this is blocked in iOS 8.

I want the video player to be static (so that horizontal videos are always horizontal, regardless of the orientation of the device), while the surface overlay control layer allows you to freely rotate.

I am using Swift.

+7
ios uiviewcontroller swift
source share
6 answers

I had this exact problem, and I quickly found that there were a lot of bad advice around autorotation, especially because iOS 8 handles it differently from previous versions.

First of all, you do not want to apply counter-rotation manually or subscribe to changes in the orientation of UIDevice . Performing counter-rotation will still lead to unsightly animation, and the orientation of the device will not always be the same as the orientation of the interface. Ideally, you want the camera preview to remain truly frozen, and your app user interface to match the orientation and size of the status bar as they change, just like your own camera app.

During orientation changes in iOS 8, the window itself rotates, and does not contain the view (s) that it contains. You can add views from multiple view controllers to a single UIWindow , but only the rootViewController will be able to respond through shouldAutorotate() . Despite the fact that you make a decision about rotation at the level of the view controller, it actually rotates the parent window, thereby rotating all its subzones (including other view controllers).

The solution consists of two UIWindow , located one above the other, each of which (or not) has its own root view controller. Most applications have only one, but there is no reason why you cannot have two and impose them just like any other UIView subclass.

Here's a working proof of concept that I also added to GitHub here . Your particular case is a little more complicated because you have a stack of contained view controllers, but the basic idea is the same. I will 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 insertion on an interfaceWindow makes it slightly larger than the borders of the screen, effectively hiding the black rectangular mask that you saw otherwise. Usually you will not 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 expect here, just add your own customization 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 cancels the negative insertion that we applied to the window, which we achieve by shifting the view the same amount and processing the contentView as the main view.

For your application, interfaceWindow.rootViewController will be your tab bar controller, which in turn contains a navigation controller, etc. All of these views should be transparent when your camera controller appears so that the camera window appears below it. For performance reasons, you can think about leaving them opaque and only set everything to transparency when the camera is actually in use, and set the camera window to hidden when it was not (when you turn off the capture session).

Sorry to write a novel; I did not see this address elsewhere, and it took me a while to understand, I hope this helps you and everyone who is trying to achieve the same behavior. Even the Apple AVCam example application does not handle this completely correctly.

An example repo I posted also includes a version with an already configured camera. Good luck

+17
source share

You can try this -

Objective-C code, if you have an alternative to fast:

 -(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { if ()//Place your condition here like if A is visible { return UIInterfaceOrientationMaskPortrait; } return UIInterfaceOrientationMaskAll; } 
+1
source share

You can subscribe to rotation change notifications and manually set the rotation transformation matrix on the sub-item that you want to rotate.

+1
source share

I'm not sure, but I think you could create your own class for your subset and override the shouldAutorotate method, etc. Therefore, it should override shouldAutorotate from parental control.

0
source share

Short answer: No, all visible controllers and views rotate (or not rotate) together.

Long answer:

First, you must implement the autorotation solution functions in the root controller; this may mean subclassing the navigation controller.

You can hack your behavior by specifying the parent view of autorotate, but manually rotate it back so that it looks without rotation.

Or you can’t autorotate, but listen to notifications that the physical device is rotated, and manually rotate all the views you want, for example: Replicating camera rotation on the iOS 6 iPhone landscape

Also see fyi:

How to force UIViewController orientation to portrait orientation in iOS 6

shouldAutoRotate Method not called in iOS6

iOS6: supportedInterfaceOrientations not working (called, but the interface still rotates)

How to implement rotation of a UIViewController in response to orientation changes?

0
source share

The simplest, most direct answer to this question is to look at the AVCam sample. The key parts for me was that he:

  • Uses a view with layerClass AVCaptureVideoPreviewLayer .
  • Sets the videoOrientation connection according to the statusBarOrientation application when presenting the view, mainly viewWillAppear(_:) .
  • Sets videoOrientation to match UIDevice.currentDevice().orientation in viewWillTransitionToSize(_:withTransitionCoordinator:) .
  • Allows autorotation and supports all interface orientations.

I implemented the background window approach described by jstn and it worked quite well, but the reality is that it is much more complicated than necessary. AVCam works great and has a relatively simple approach.

0
source share

All Articles