如何在iOS 8中强制查看控制器方向?

时间:2014-10-14 09:26:50

标签: ios objective-c ios8 uinavigationcontroller autorotate

在iOS 8之前,我们将以下代码与 supportedInterfaceOrientations shouldAutoRotate 委托方法结合使用,以强制将应用定位到任何特定方向。我使用下面的代码段以编程方式将应用程序旋转到所需的方向。首先,我正在改变状态栏方向。然后只显示并立即关闭模态视图会将视图旋转到所需的方向。

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

但这在iOS 8中失败了。此外,我在堆栈溢出中看到了一些答案,人们建议我们应该始终从iOS 8开始避免这种方法。

更具体地说,我的应用程序是一种通用类型的应用程序。总共有三个控制器。

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

  2. 第二个视图控制器 - 它应该只支持所有条件下的横向

  3. 第三视图控制器 - 它应该只支持所有条件下的横向

  4. 我们正在使用导航控制器进行页面导航。从第一个视图控制器,在按钮单击操作上,我们在堆栈上推送第二个。因此,当第二个视图控制器到达时,无论设备方向如何,应用程序都应仅锁定横向。

    以下是第二和第三个视图控制器中的shouldAutorotatesupportedInterfaceOrientations方法。

    -(NSUInteger)supportedInterfaceOrientations{
        return UIInterfaceOrientationMaskLandscapeRight;
    }
    
    -(BOOL)shouldAutorotate {
        return NO;
    }
    

    对于这种或任何更好的方法来锁定iOS 8的特定方向的视图控制器是否有任何解决方案。请帮助!!

24 个答案:

答案 0 :(得分:298)

对于iOS 7 - 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:中调用它即可。

答案 1 :(得分:91)

如果您位于UINavigationControllerUITabBarController内,方向旋转会更复杂一些。问题是如果视图控制器嵌入其中一个控制器中,导航或标签栏控制器优先,并决定自动旋转和支持的方向。

我在UINavigationController和UITabBarController上使用以下2个扩展,以便嵌入其中一个控制器的视图控制器可以做出决定。

为视图控制器提供电源!

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,否则可以忽略要继承默认值的其他视图控制器中的覆盖应用程序的plist中指定的方向行为

禁用旋转

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

锁定到特定方向

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

理论上这适用于所有复杂的视图控制器层次结构,但我注意到UITabBarController存在问题。出于某种原因,它想要使用默认方向值。如果您有兴趣了解如何解决某些问题,请参阅以下博客文章:

Lock Screen Rotation

答案 2 :(得分:23)

这对我有用:

https://developer.apple.com/library//ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/clm/UIViewController/attemptRotationToDeviceOrientation

在viewDidAppear:方法中调用它。

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

    [UIViewController attemptRotationToDeviceOrientation];
}

答案 3 :(得分:20)

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

夫特:

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

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

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

答案 4 :(得分:15)

这种方式适用于 Swift 2 iOS 8.x:

PS(此方法不需要在每个viewController上覆盖定向函数,例如shouldautorotate,只需在AppDelegate上使用一个方法)

检查项目常规信息中的“需要全屏”。 enter image description here

因此,在AppDelegate.swift上创建一个变量:

var enableAllOrientation = false

所以,也把这个功能:

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

因此,在项目的每个类中,您都可以在viewWillAppear中设置此var:

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
        }
    }

答案 5 :(得分:9)

这应该可以从iOS 6开始运行,但我只在iOS 8上测试它。子类UINavigationController并覆盖以下方法:

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

- (BOOL)shouldAutorotate {
    return NO;
}

或询问可见视图控制器

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

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

并在那里实施这些方法。

答案 6 :(得分:9)

这是对Sid Shah's answer中评论的反馈,关于如何使用以下方式禁用动画:

[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];
}

答案 7 :(得分:9)

首先 - 这是一个坏主意,一般来说,您的应用架构出现问题,但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)

全部

答案 8 :(得分:6)

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
}

答案 9 :(得分:6)

如果您正在使用navigationViewController,则应为此创建自己的超类并覆盖:

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

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

  return YES;
}

这将禁用 SecondViewController 中的旋转,但如果您在设备处于纵向方向时推动 SecondViewController ,则 SecondViewController 将以纵向显示模式。

假设您正在使用故事板。您必须在“onClick”方法中创建手动segue(How to):

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

这会在超类禁用自动旋转功能之前强制横向显示。

答案 10 :(得分:4)

我尝试过很多解决方案,但有效的方法如下:

无需编辑ios 8和9中的info.plist

- (BOOL) shouldAutorotate {
    return NO;
}   

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

Possible orientations from the Apple Documentation

  

<强> UIInterfaceOrientationUnknown

     

无法确定设备的方向。

     

<强> UIInterfaceOrientationPortrait

     

设备处于纵向模式,设备保持直立状态   底部的主页按钮。

     

<强> UIInterfaceOrientationPortraitUpsideDown

     

设备处于纵向模式但是上下颠倒,设备保持不动   直立和顶部的主页按钮。

     

<强> UIInterfaceOrientationLandscapeLeft

     

设备处于横向模式,设备保持直立状态   左侧的主页按钮。

     

<强> UIInterfaceOrientationLandscapeRight

     

设备处于横向模式,设备保持直立状态   右侧的主页按钮。

答案 11 :(得分:3)

Sids和Koreys答案的组合对我有用。

扩展导航控制器:

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

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

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

在segue之前旋转到适当的方向

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

答案 12 :(得分:2)

[iOS9 +] 如果有人一直拖到这里,因为以上解决方案都没有工作, 如果您提供视图,则需要更改方向 通过segue,您可能想检查一下。

检查您的segue的演示文稿类型。 我的目前背景是“我的”。 当我将其更改为全屏时,它有效。

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

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

答案 13 :(得分:2)

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

答案 14 :(得分:2)

上面的最佳解决方案:

SELECT ta.topcallid,
       CASE 
            WHEN ta.e_created < Trans.e_created AND ta.e_terminated > Trans.e_terminated THEN 'Consult (Stage ' + CAST(TA.EmpNum AS VARCHAR(3)) + ' To ' + CAST(Trans.EmpNum AS VARCHAR(3)) + ')'
            WHEN ta.e_terminated < Trans.e_created THEN 'Cold Transfer (Stage ' + CAST(TA.EmpNum AS VARCHAR(3)) + ' To ' + CAST(Trans.EmpNum AS VARCHAR(3)) + ')'
            WHEN ta.e_terminated > Trans.e_created THEN 'Warm Transfer (Stage ' + CAST(TA.EmpNum AS VARCHAR(3)) + ' To ' + CAST(Trans.EmpNum AS VARCHAR(3)) + ')'
       END TransStatus
FROM   [TransferTypeAnalysis] TA
       JOIN [TransferTypeAnalysis] Trans
            ON  TA.topcallid = Trans.topcallid
                AND ta.empnum < Trans.empnum
ORDER BY
       TA.topcallid,
       ta.empnum
当我在所呈现的视图控制器的viewDidAppear中调用它时,

对我没有用。但是,当我在呈现视图控制器中的preparForSegue中调用它时,它确实有效。

(抱歉,没有足够的声誉可以评论该解决方案,所以我必须像这样添加它)

答案 15 :(得分:1)

根据Korey Hinton's answer

Swift 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
}

答案 16 :(得分:1)

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

我使用TabbedViewController的工作示例创建了以下 objective-c 项目github.com/GabLeRoux/RotationLockInTabbedViewChild,其中一个子视图允许旋转,另一个子视图被锁定在纵向

它并不完美但它有效并且同样的想法应该适用于其他类型的根视图,例如NavigationViewController。 :)

Child view locks parent orientation

答案 17 :(得分:0)

关于如何最好地完成这项任务似乎还存在争议,所以我想我会分享我的(工作)方法。在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.

以外的其他内容,只需更改要强制使用的具体方向即可

我的关键是attemptRotationToDeviceOrientation中的viewWillAppear来电 - 如果没有物理旋转设备,视图将无法正常旋转。

答案 18 :(得分:0)

使用此方法锁定视图控制器方向,在IOS 9上测试:

//将方向锁定为横向右侧

-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight;
}

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

答案 19 :(得分:0)

我的解决方案

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's solution适用于任何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
    }

}

答案 20 :(得分:0)

根据@ 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"];
    }
}

答案 21 :(得分:0)

我有同样的问题,浪费了很多时间。所以现在我有了解决方案。我的应用程序设置仅支持纵向。但是,我的应用程序中的某些屏幕只需要横向。我通过在 AppDelegate 处设置变量 isShouldRotate 来修复它。以及 AppDelegate 的功能:

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

最后当 ViewControllerA 需要横向状态时。只需这样做: 在推送/呈现之前 ViewControllerA isShouldRotate 指定为 true 。不要忘记当控制器在 viewWillDisappear 处将 isShouldRotate 分配给 false 时弹出/关闭。

答案 22 :(得分:0)

对我来说,顶级VC需要实现方向覆盖。如果顶级VC未实现,则使用VC向下堆栈将无效。

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

只收听VC-Main,主要是在我的测试中。

答案 23 :(得分:0)

看来,即使您在这里有这么多答案,也没有人对我足够。我想强制定位,然后再回到设备定位,但是[UIViewController attemptRotationToDeviceOrientation];没用。使整个事情变得复杂的是,我根据一些答案添加了shouldAutorotate为false,并且在所有情况下都无法获得理想的效果来正确地向后旋转。

这就是我所做的:

在将控制器推入他的init构造函数中的调用之前,

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

因此,我保存了上一个设备方向并注册了方向更改事件。方向更改事件很简单:

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

在视线散开的情况下,我只是强制返回到userOreintation所具有的任何方向:

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

这也必须存在:

- (BOOL)shouldAutorotate {
    return true;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

而且导航控制器还必须委派给shouldAutorotate和supportedInterfaceOrientations,但我相信大多数人已经知道了。

PS:抱歉,我使用了一些扩展名和基类,但是名称很有意义,因此概念是可以理解的,它将进行更多的扩展,因为现在还不太好。