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.
Swipe down from the top-right corner of your screen to open Control Center. Tap the Portrait Orientation Lock button to make sure that it's off.
Swipe down from the top right-hand corner of your screen to open Control Centre. Tap the Portrait Orientation Lock button to make sure it's turned off. Turn your iPhone sideways.
The UIViewController class defines the shared behavior that's common to all view controllers. You rarely create instances of the UIViewController class directly. Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller's view hierarchy.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With