如何检测屏幕方向变化?

173

我正在使用Swift,当我旋转到横向模式时,我想要能够加载一个UIViewController,有人可以指点我一下吗?

我在网上找不到任何信息,对文档感到有点困惑。


1
我猜API没有改变,所以应该是“didRotateToOrientation”和“willRotateToOrientation”,类似这样的东西,请查看苹果文档。 - David Ansermot
1
嗨@mArm.ch,感谢您的快速回复!那么我该如何实现呢?(这是我的第一个应用程序...我对IOS非常陌生) :) - David
我将其转发为答案,供其他人参考。如果您觉得可以的话,您能否接受它? - David Ansermot
如果您想在应用程序启动时检查方向,请检查此内容。 https://dev59.com/clsW5IYBdhLWcg3w7KtL#49058588 - eharo2
24个回答

221

以下是我如何使它工作的:

AppDelegate.swift中的didFinishLaunchingWithOptions函数内,我添加了以下代码:

NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)

然后在AppDelegate类内部,我加入了以下函数:

func rotated() {
    if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
        print("Landscape")
    }

    if UIDeviceOrientationIsPortrait(UIDevice.current.orientation) {
        print("Portrait")
    }
}

5
我尝试在AppDelegate中添加addObserver,但是CoreFoundation报告了一个未被识别的选择器(SIGABRT)。然而,当我将addObserver移到我的第一个视图的viewDidLoad中时,它完美地运行了。如果其他人遇到相同的问题,这只是提供一些信息。 - FractalDoctor
1
我是编程新手,但 selector 的字符串格式不应该是 "rotated:" 吗? - Chameleon
4
我相信这仅适用于当您接受参数时(而rotated()并不接受参数)。 - David
35
请注意,UIDeviceOrientationUIInterfaceOrientation是不同的东西。这是因为UIDeviceOrientation还可以检测面朝下和面朝上的方向,这意味着如果您的设备静置在接近平坦但略微不平整的表面上(例如6/6s相机凸出部分上晃动),那么您的代码可能会随机在纵向和横向之间跳转。 - liamnichols
6
如果手机禁用了自动旋转,这种方法将无法使用。 - chengsam
显示剩余12条评论

192
根据Apple文档: 当视图控制器的视图大小由其父视图更改时调用此方法(例如,当窗口旋转或调整大小时用于根视图控制器)。
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    }
    if UIDevice.current.orientation.isFlat {
        print("Flat")
    } else {
        print("Portrait")
    }
}

74
这将被称为旋转之前。如果您需要旋转后的框架尺寸,这个解决方案将不起作用。 - DarkLeafyGreen
扩展中无法工作。横向或纵向打印仍然会打印“纵向”。 - TomSawyer
7
如果手机禁用了自动旋转,这种方法将无法使用。 - chengsam
6
在 iPad 上,由于横屏和竖屏的尺寸类别相同,因此这种方法从未被调用。 - LiangWang
如果设备返回isFlat,则这也会失败。 - CodeBender
看到那些人每次都忘记调用super真是令人痛苦...人们需要理解override的含义。 - pierre23

67

我需要在使用 AVFoundation 摄像头时检测旋转,发现 didRotate (现已弃用) 和 willTransition 方法无法满足我的需求。使用 David 发布的通知确实有效,但不适用于 Swift 3.x 及以上版本。

以下使用了闭包,这似乎是 Apple 推荐的方式。

var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other (such as face up & down)")
        }
    }

设置通知:

NotificationCenter.default.addObserver(forName: UIDevice.orientationDidChangeNotification,
                                       object: nil,
                                       queue: .main,
                                       using: didRotate)

撤销通知:

NotificationCenter.default.removeObserver(self,
                                          name: UIDevice.orientationDidChangeNotification,
                                          object: nil)

关于弃用声明: 我最初的评论可能有误导性,所以我想进行更新。如注所述,使用@objc推断已被弃用,这反过来需要使用#selector。通过使用闭包,可以避免这种情况,并且现在您有一个解决方案,应该避免调用无效选择器而导致崩溃。


4
你有苹果公司关于Swift 4中不鼓励使用#selector的参考资料吗?我想了解他们为什么这样说。 - jeffjv
1
我已经添加了一个链接到swift-evolution,讨论这个变化。 - CodeBender
1
@CodeBender:编译器警告并不意味着你所说的那样。#selector 并没有被弃用,只有 "@objc" 推断被弃用了。这意味着当你将一个函数用作 #selector 时,你必须显式地标记它,以便编译器会生成额外的正确代码,因为编译器将不再尝试从你的使用中推断它。因此,如果你在你的 Swift 3.0 解决方案中的 rotate() 函数中添加 "@obj",那么代码将会编译而无警告。 - Mythlandia
感谢@Mythlandia,我更新了答案以解决我最初陈述中的混淆。 - CodeBender
如何在didRotate变量中访问类变量或函数?有什么想法吗? - Berlin
显示剩余2条评论

21

使用UIDevice-orientation属性是不正确的(即使在大多数情况下它可能会工作),并且可能会导致一些错误,例如UIDeviceOrientation还考虑设备是否面朝上或面朝下,而对于这些值,UIInterfaceOrientation枚举中没有直接的对应项。
此外,如果您锁定了应用程序在某个特定方向上,则UIDevice将给出不考虑该方向的设备方向。
另一方面,iOS8已经弃用了UIViewController类上的interfaceOrientation属性。
有两个选项可用于检测界面方向:

  • 使用状态栏方向
  • 使用尺寸类,在iPhone上,如果它们没有被覆盖,它们可以为您提供了解当前界面方向的方法

仍然缺少的是了解界面方向更改方向的方法,这在动画过程中非常重要。
在WWDC 2014“ iOS8中的视图控制器进展”会议上,演讲者还提供了解决该问题的方法,使用替换-will/DidRotateToInterfaceOrientation的方法。

这里提出的解决方案部分实现,更多信息here:

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        let orientation = orientationFromTransform(coordinator.targetTransform())
        let oldOrientation = UIApplication.sharedApplication().statusBarOrientation
        myWillRotateToInterfaceOrientation(orientation,duration: duration)
        coordinator.animateAlongsideTransition({ (ctx) in
            self.myWillAnimateRotationToInterfaceOrientation(orientation,
            duration:duration)
            }) { (ctx) in
                self.myDidAnimateFromInterfaceOrientation(oldOrientation)
        }
    }

13

iOS 8以后,这是正确的方法。

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    coordinator.animate(alongsideTransition: { context in
        // This is called during the animation
    }, completion: { context in
        // This is called after the rotation is finished. Equal to deprecated `didRotate`
    })
}

NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.rotated), name: UIDevice.orientationDidChangeNotification, object: nil) 更好的答案。UIDeviceOrientation 的问题在于它检测到我们不需要的面朝下和面朝上的方向。 - Kirill

12

我知道这个问题是关于Swift的,但由于它是谷歌搜索中排名靠前的链接之一,如果你想在Objective-C中寻找相同的代码:

// add the observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotated:) name:UIDeviceOrientationDidChangeNotification object:nil];

// remove the observer
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];

// method signature
- (void)rotated:(NSNotification *)notification {
    // do stuff here
}

11

我喜欢检查方向通知,因为您可以在任何类中添加此功能,无需是视图或视图控制器。甚至可以在您的应用程序委托中使用。

SWIFT 5:

    //ask the system to start notifying when interface change
    UIDevice.current.beginGeneratingDeviceOrientationNotifications()
    //add the observer
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(orientationChanged(notification:)),
        name: UIDevice.orientationDidChangeNotification,
        object: nil)

比起缓存通知

    @objc func orientationChanged(notification : NSNotification) {
        //your code there
    }

11

非常简单,在iOS8、9/Swift2/Xcode7中有效,只需将以下代码放入您的viewcontroller.swift中。每次屏幕方向更改时,它都会打印屏幕尺寸,您可以放置自己的代码代替:

override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
        getScreenSize()
    }
    var screenWidth:CGFloat=0
    var screenHeight:CGFloat=0
    func getScreenSize(){
        screenWidth=UIScreen.mainScreen().bounds.width
        screenHeight=UIScreen.mainScreen().bounds.height
        print("SCREEN RESOLUTION: "+screenWidth.description+" x "+screenHeight.description)
    }

5
此函数已被弃用,请使用"viewWillTransitionToSize:withTransitionCoordinator: "代替。 - Masa S-AiYa
除了被弃用之外,didRotateFromInterfaceOrientation() 的可靠性不高。它会错过一些旋转。iewWillTransitionToSize:withTransitionCoordinator: 运行良好。 - Andrej
@Andrej 因为 Swift 3,许多东西现在已经过时了。 - Josh


8

在Objective C中

-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator

在Swift中
func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

重写此方法以检测方向更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接