如何在 iOS8中强制视图控制器定位?

在 iOS8之前,我们使用以下代码结合 支持的界面定位应该自动旋转委托方法来强制应用程序朝向任何特定的方向。我使用下面的代码片段,以编程方式将应用程序旋转到所需的方向。首先,我要改变状态栏的方向。然后,只要呈现并立即取消一个模态视图,就可以将视图旋转到所需的方向。

[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight animated:YES];
UIViewController *c = [[UIViewController alloc]init];
[self presentViewController:vc animated:NO completion:nil];
[self dismissViewControllerAnimated:NO completion:nil];

但是在 iOS8中这是失败的。此外,我还看到了一些关于堆栈溢出的答案,其中有人建议我们应该从 iOS8开始一直避免使用这种方法。

更具体地说,我的应用程序是一种通用类型的应用程序。

  1. 第一视图控制器 -它应该支持 iPad 中的所有方向,并且在 iPhone 中只支持肖像(主页按钮向下)。

  2. 第二视图控制器 -它应该在所有条件下只支持景观

  3. 第三视图控制器 -它应该在所有条件下只支持景观

我们使用导航控制器进行页面导航。从第一个视图控制器,在一个按钮点击动作,我们推动第二个堆栈。因此,当第二个视图控制器到达时,不管设备方向如何,应用程序应该只锁定横向。

下面是我的 shouldAutorotatesupportedInterfaceOrientations方法在第二和第三视图控制器。

-(NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskLandscapeRight;
}


-(BOOL)shouldAutorotate {
return NO;
}

有没有解决这个问题的方法,或者有没有更好的方法来锁定 iOS8中特定方向的视图控制器。救命啊!

156922 次浏览

对于 iOS7-10:

目标 C:

[[UIDevice currentDevice] setValue:@(UIInterfaceOrientationLandscapeLeft) forKey:@"orientation"];
[UINavigationController attemptRotationToDeviceOrientation];

斯威夫特3:

let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()

只需在提供的视图控制器的 - viewDidAppear:中调用它。

如果你在 UINavigationController或者 UITabBarController中,方向旋转会稍微复杂一点。问题是,如果视图控制器嵌入在这些控制器之一,导航或标签栏控制器的优先权,并作出决定的自旋和支持的方向。

我在 UINavigationController 和 UITabBarController 上使用了以下两个扩展,以便嵌入在这些控制器之一中的视图控制器能够做出决策。

给视图控制器权力!

Swift 2.3

extension UINavigationController {
public override func supportedInterfaceOrientations() -> Int {
return visibleViewController.supportedInterfaceOrientations()
}
public override func shouldAutorotate() -> Bool {
return visibleViewController.shouldAutorotate()
}
}


extension UITabBarController {
public override func supportedInterfaceOrientations() -> Int {
if let selected = selectedViewController {
return selected.supportedInterfaceOrientations()
}
return super.supportedInterfaceOrientations()
}
public override func shouldAutorotate() -> Bool {
if let selected = selectedViewController {
return selected.shouldAutorotate()
}
return super.shouldAutorotate()
}
}

Swift 3

extension UINavigationController {
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return visibleViewController?.supportedInterfaceOrientations ?? super.supportedInterfaceOrientations
}


open override var shouldAutorotate: Bool {
return visibleViewController?.shouldAutorotate ?? super.shouldAutorotate
}
}


extension UITabBarController {
open override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if let selected = selectedViewController {
return selected.supportedInterfaceOrientations
}
return super.supportedInterfaceOrientations
}


open override var shouldAutorotate: Bool {
if let selected = selectedViewController {
return selected.shouldAutorotate
}
return super.shouldAutorotate
}
}

现在你可以重写 supportedInterfaceOrientations方法,或者你可以重写想要锁定的视图控制器中的 shouldAutoRotate,否则你可以忽略其他视图控制器中的重写,这些视图控制器你想要继承应用程序列表中指定的默认方向行为

关闭旋转

class ViewController: UIViewController {
override func shouldAutorotate() -> Bool {
return false
}
}

锁定特定方向

class ViewController: UIViewController {
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}
}

理论上,这应该适用于所有复杂的视图控制器层次结构,但是我注意到了 UITabBarController 的一个问题。出于某种原因,它希望使用默认的方向值。如果你有兴趣学习如何解决这些问题,请看下面的博客文章:

锁定屏幕旋转

这应该可以从 iOS6向上运行,但我只在 iOS8上测试过。子类 UINavigationController并重写以下方法:

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return UIInterfaceOrientationLandscapeRight;
}


- (BOOL)shouldAutorotate {
return NO;
}

或者询问可见视图控制器

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
return self.visibleViewController.preferredInterfaceOrientationForPresentation;
}


- (BOOL)shouldAutorotate {
return self.visibleViewController.shouldAutorotate;
}

并在那里实现方法。

希德和科里斯的答案组合对我很有效。

扩展导航控制器:

extension UINavigationController {
public override func shouldAutorotate() -> Bool {
return visibleViewController.shouldAutorotate()
}
}

然后在单个视图上禁用旋转

class ViewController: UIViewController {
override func shouldAutorotate() -> Bool {
return false
}
}

然后在转场之前旋转到合适的方向

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "SomeSegue")
{
let value = UIInterfaceOrientation.Portrait.rawValue;
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
}

我发现,如果它是一个呈现的视图控制器,您可以覆盖 preferredInterfaceOrientationForPresentation

斯威夫特:

override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}


override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return UIInterfaceOrientation.LandscapeLeft
}


override func shouldAutorotate() -> Bool {
return false
}

这就是对我有效的方法:

Https://developer.apple.com/library//ios/documentation/uikit/reference/uiviewcontroller_class/index.html#//apple_ref/occ/clm/uiviewcontroller/attemptrotationtodeviceorientation

在 viewDidAppear: method 中调用它。

- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];


[UIViewController attemptRotationToDeviceOrientation];
}

上面提到的解决方案:

let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")

当我在 viewDidAppear 中调用它时,它并没有为我工作。但是,当我在 Presentview 控制器中调用 preforSegue 时,它确实工作了。

(对不起,没有足够的声誉点来评论这个解决方案,所以我不得不这样添加它)

这是对 Sid Shah 的回答中关于如何使用以下方法禁用动画的注释的反馈:

[UIView setAnimationsEnabled:enabled];

密码:

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:NO];
[UIView setAnimationsEnabled:NO];


// Stackoverflow #26357162 to force orientation
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
}


- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:NO];
[UIView setAnimationsEnabled:YES];
}

我也有同样的问题,为此浪费了太多时间。现在我有办法了。我的应用程序设置只支持肖像。然而,一些屏幕进入我的应用程序需要有横向只。我修复它有一个变量 应该旋转应用代理应用代理的功能是:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
if isShouldRotate == true {
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}

最后当 ViewControllerA需要景观状态时。只要这样做: 在推之前ViewControllerA分配 应该旋转没错。不要忘记什么时候弹出/关闭该控制器分配 应该旋转假的消失

如果您正在使用导航 ViewController,那么您应该为此创建自己的超类并重写:

- (BOOL)shouldAutorotate {
id currentViewController = self.topViewController;


if ([currentViewController isKindOfClass:[SecondViewController class]])
return NO;


return YES;
}

这将禁用旋转的 Second ViewController 第二视图控制器,但如果你推动你的 Second ViewController 第二视图控制器时,你的设备是在纵向方向,那么你的 Second ViewController 第二视图控制器将出现在纵向模式。

假设你正在使用情节串连图板,你必须创建手动接续(怎么做) ,并在你的“ onClick”方法中:

- (IBAction)onPlayButtonClicked:(UIBarButtonItem *)sender {
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
[self performSegueWithIdentifier:@"PushPlayerViewController" sender:self];
}

这将强制横向方向之前,您的超类禁用自旋功能。

根据@sid-sha 提供的解决方案,你必须把所有东西都放在 viewDidAppear:方法中,否则你不会让 didRotateFromInterfaceOrientation:被解雇,所以类似于:

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIInterfaceOrientation interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
NSNumber *value = [NSNumber numberWithInt:interfaceOrientation];
[[UIDevice currentDevice] setValue:value forKey:@"orientation"];
}
}

这种方法对我来说在 Swift 2iOS8.x:

PS (这个方法不需要覆盖方向函数,比如应该在每个 viewController 上自动旋转,只需要在 AppDelegate 上使用一个方法)

检查“需要全屏幕”在您的项目一般信息。 enter image description here

因此,在 AppGenerate.swift 上做一个变量:

var enableAllOrientation = false

那么,再加上这个函数:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if (enableAllOrientation == true){
return UIInterfaceOrientationMask.All
}
return UIInterfaceOrientationMask.Portrait
}

因此,在项目的每个类中,都可以在 viewWillAppear 中设置这个变量:

override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.enableAllOrientation = true
}

如果你需要根据设备类型做出选择,你可以这样做:

override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
switch UIDevice.currentDevice().userInterfaceIdiom {
case .Phone:
// It's an iPhone
print(" - Only portrait mode to iPhone")
appDelegate.enableAllOrientation = false
case .Pad:
// It's an iPad
print(" - All orientation mode enabled on iPad")
appDelegate.enableAllOrientation = true
case .Unspecified:
// Uh, oh! What could it be?
appDelegate.enableAllOrientation = false
}
}

根据 科里 · 辛顿的回答

迅捷2.2:

extension UINavigationController {
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return visibleViewController!.supportedInterfaceOrientations()
}
public override func shouldAutorotate() -> Bool {
return visibleViewController!.shouldAutorotate()
}
}




extension UITabBarController {
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if let selected = selectedViewController {
return selected.supportedInterfaceOrientations()
}
return super.supportedInterfaceOrientations()
}
public override func shouldAutorotate() -> Bool {
if let selected = selectedViewController {
return selected.shouldAutorotate()
}
return super.shouldAutorotate()
}
}

关闭旋转

override func shouldAutorotate() -> Bool {
return false
}

锁定特定方向

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.Portrait
}

我的要求是

  1. 以纵向模式锁定所有视图
  2. 使用 AVPlayerViewController播放视频

当视频播放时,如果它是一个景观,然后允许屏幕旋转景观右侧和景观左侧。如果它是一个肖像,然后锁定视图在肖像模式只。

首先,在 AppDelegate.swift中定义 supportedInterfaceOrientationsForWindow

var portrait = true
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if portrait {
return .Portrait
} else {
return .Landscape
}
}

其次,在主视图控制器中定义以下函数

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
print("\(#function)")
return .Portrait
}


override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return .Portrait
}


override func shouldAutorotate() -> Bool {
return false
}

然后,您需要子类 AVPlayerViewController

class MyPlayerViewController: AVPlayerViewController {


var size: CGSize?


var supportedOrientationMask: UIInterfaceOrientationMask?
var preferredOrientation: UIInterfaceOrientation?


override func viewDidLoad() {
super.viewDidLoad()


if let size = size {
if size.width > size.height {
self.supportedOrientationMask =[.LandscapeLeft,.LandscapeRight]
self.preferredOrientation =.LandscapeRight
} else {
self.supportedOrientationMask =.Portrait
self.preferredOrientation =.Portrait
}
}
}

MyPlayerViewController.swift中重写这三个函数

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return self.supportedOrientationMask!
}


override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return self.preferredOrientation!
}

因为用户可能旋转设备横向左或横向右,我们需要设置自动旋转为真

override func shouldAutorotate() -> Bool {
return true
}

最后,在视图控制器中创建 MyPlayerViewController实例并设置属性大小值。

let playerViewController = MyPlayerViewController()


// Get the thumbnail
let thumbnail = MyAlbumFileManager.sharedManager().getThumbnailFromMyVideoMedia(......)


let size = thumbnail?.size
playerViewController.size = size

使用适当的 videoUrl初始化你的播放器,然后将你的播放器分配到 playerViewController。编码愉快!

我的解决办法

AppDelegate:

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
if let topController = UIViewController.topMostViewController() {
if topController is XXViewController {
return [.Portrait, .LandscapeLeft]
}
}
return [.Portrait]
}

XXViewController是您希望支持横向模式的 ViewController。

然后 Sunny Shah 的解决方案会在任何 iOS 版本的 XXViewController中工作:

let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")

这是查找最顶层 ViewController 的实用程序函数。

extension UIViewController {


/// Returns the current application's top most view controller.
public class func topMostViewController() -> UIViewController? {
let rootViewController = UIApplication.sharedApplication().windows.first?.rootViewController
return self.topMostViewControllerOfViewController(rootViewController)
}






/// Returns the top most view controller from given view controller's stack.
class func topMostViewControllerOfViewController(viewController: UIViewController?) -> UIViewController? {
// UITabBarController
if let tabBarController = viewController as? UITabBarController,
let selectedViewController = tabBarController.selectedViewController {
return self.topMostViewControllerOfViewController(selectedViewController)
}


// UINavigationController
if let navigationController = viewController as? UINavigationController,
let visibleViewController = navigationController.visibleViewController {
return self.topMostViewControllerOfViewController(visibleViewController)
}


// presented view controller
if let presentedViewController = viewController?.presentedViewController {
return self.topMostViewControllerOfViewController(presentedViewController)
}


// child view controller
for subview in viewController?.view?.subviews ?? [] {
if let childViewController = subview.nextResponder() as? UIViewController {
return self.topMostViewControllerOfViewController(childViewController)
}
}


return viewController
}


}

我已经尝试了很多方法,但最有效的方法是:

没有必要在 ios8和 ios9中编辑 info.plist

- (BOOL) shouldAutorotate {
return NO;
}


- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return (UIInterfaceOrientationPortrait | UIInterfaceOrientationPortraitUpsideDown);
}

苹果文档的可能方向 :

UIInterfaceOrientationUnknown

无法确定设备的方向。

面向界面的肖像

该设备是在纵向模式,与设备举行垂直和 底部的 Home 键。

面向人物肖像

该设备是在纵向模式,但倒置,与设备举行 直立,顶部的 home 按钮。

界面定位,景观左边

该设备是在横向模式,与设备举行垂直和 左边的 Home 键。

UIInterfaceOrientationLandscape

该设备是在横向模式,与设备举行垂直和 右边的 Home 键。

Xcode 8上,这些方法被转换为属性,因此以下方法可以用于 Swift:

override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}


override public var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return UIInterfaceOrientation.portrait
}


override public var shouldAutorotate: Bool {
return true
}

使用这个来锁定视图控制器的方向,在 IOS 9上测试:

向右侧锁定

-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeRight;
}


-(NSUInteger)navigationControllerSupportedInterfaceOrientations:(UINavigationController *)navigationController {
return UIInterfaceOrientationMaskLandscapeRight;
}

关于如何最好地完成这项任务,似乎还有一些争论,所以我想分享一下我的(工作)方法。在 UIViewController实现中添加以下代码:

- (void) viewWillAppear:(BOOL)animated
{
[UIViewController attemptRotationToDeviceOrientation];
}


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}


-(BOOL)shouldAutorotate
{
return NO;
}


- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscapeLeft;
}

在这个例子中,您还需要在项目设置中(或直接在 info.plist中)将允许的设备方向设置为“横向左侧”。如果你想要的不是 LandscapeLeft.,只需要改变你想要强制的特定方向

对我来说,关键是 viewWillAppear中的 attemptRotationToDeviceOrientation调用——如果没有 viewWillAppear,视图就不能正常旋转,除非物理旋转设备。

我在这里尝试了一些解决方案,重要的是要明白,它的根视图控制器将决定它是否会旋转。

我创建了下面的 目标 c项目 Github.com/gableroux/rotationlockintabbedviewchild与一个工作的例子 TabbedViewController,其中一个子视图是允许旋转的,另一个子视图是锁定在纵向。

它并不完美,但是它可以工作,同样的思想应该适用于其他类型的根视图,比如 NavigationViewController。:)

Child view locks parent orientation

首先,这是一个坏主意,一般来说,你的应用程序架构出了问题,但是,sh..t happens,如果是这样的话,你可以尝试做下面这样的东西:

final class OrientationController {


static private (set) var allowedOrientation:UIInterfaceOrientationMask = [.all]


// MARK: - Public


class func lockOrientation(_ orientationIdiom: UIInterfaceOrientationMask) {
OrientationController.allowedOrientation = [orientationIdiom]
}


class func forceLockOrientation(_ orientation: UIInterfaceOrientation) {
var mask:UIInterfaceOrientationMask = []
switch orientation {
case .unknown:
mask = [.all]
case .portrait:
mask = [.portrait]
case .portraitUpsideDown:
mask = [.portraitUpsideDown]
case .landscapeLeft:
mask = [.landscapeLeft]
case .landscapeRight:
mask = [.landscapeRight]
}


OrientationController.lockOrientation(mask)


UIDevice.current.setValue(orientation.rawValue, forKey: "orientation")
}
}

然后,在 AppDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// do stuff
OrientationController.lockOrientation(.portrait)
return true
}


// MARK: - Orientation


func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return OrientationController.allowedOrientation
}

无论何时你想改变方向,都要这样做:

OrientationController.forceLockOrientation(.landscapeRight)

注意: 有时,设备可能不会从这样的呼叫更新,所以你可能需要做如下

OrientationController.forceLockOrientation(.portrait)
OrientationController.forceLockOrientation(.landscapeRight)

仅此而已

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[UIViewController attemptRotationToDeviceOrientation];
}

[ iOS9 + ] 如果有人因为上述方法都不管用而一路拖到这里, 如果呈现视图,则需要更改方向 接下来,你最好看看这个。

检查你的演讲类型。 我的是“在当前环境下”。 当我把它改成 Fullscreen 的时候,它起作用了。

感谢@GabLeRoux,我找到了这个解决方案。

  • 这种更改只有在与上述解决方案结合使用时才有效。

对我来说,顶级 VC 需要实现方向覆盖。如果顶级 VC 没有实现,那么使用 VC 的下层堆栈将不会有任何效果。

VC-main
|
-> VC 2
|
-> VC 3

基本上在我的测试中,只有 VC-Main 被监听。

看来即使你在这里也有很多答案没有人能满足我。我想强制定位,然后回到设备定位,但 [UIViewController attemptRotationToDeviceOrientation];就是不工作。还有一件复杂的事情,就是我根据一些答案将 should Autorotate 添加到 false,但是在所有场景中都无法获得正确的旋转回来的效果。

所以我就这么做了:

在调用 init 构造函数中的 controller 之前:

_userOrientation = UIDevice.currentDevice.orientation;
[UIDevice.currentDevice setValue:@(UIInterfaceOrientationPortrait) forKey:@"orientation"];
[self addNotificationCenterObserver:@selector(rotated:)
name:UIDeviceOrientationDidChangeNotification];

所以我保存了最后一个设备方向并注册了方向改变事件。方向改变事件很简单:

- (void)rotated:(NSNotification*)notification {
_userOrientation = UIDevice.currentDevice.orientation;
}

在视频中,我只是强迫自己回到我作为用户的任何方向:

- (void)onViewDismissing {
super.onViewDismissing;
[UIDevice.currentDevice setValue:@(_userOrientation) forKey:@"orientation"];
[UIViewController attemptRotationToDeviceOrientation];
}

这个也必须在那里:

- (BOOL)shouldAutorotate {
return true;
}


- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}

而且导航控制器必须授权给 should Autorotate 并且支持 InterfaceOrientations,但是我相信大多数人已经有了。

附言: 对不起,我使用了一些扩展和基类,但是名称是非常有意义的,所以概念是可以理解的,将使更多的扩展,因为它不是太漂亮现在。