如何检测方向变化?


148

我使用的是Swift,我希望能够在旋转到横向时加载UIViewController,有人可以指出正确的方向吗?

我在网上找不到任何东西,并且对文档有些困惑。


1
我想API并没有改变,因此应该是“ didRotateToOrientation”和“ willRotateToOrientation”,类似的东西,请查阅Apple文档
David'mArm'Ansermot 2014年

1
@ mArm.ch,您好,感谢您的快速回复!那么我将如何实现呢?(这是我的第一个应用程序……我对IOS还是很陌生):)
David David

我改贴了其他人的答案。如果可以的话,您可以接受吗?
David'mArm'Ansermot 2014年

Answers:


194

这是我的工作方式:

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 2014年

1
我是编码新手,但不应selector使用"rotated:"?? 的字符串格式
变色龙2015年

4
我很确定,只有在您接受论点的情况下(那rotated()
David

26
请注意,因为UIDeviceOrientation它与UIInterfaceOrientation.. 有所不同。这是因为UIDeviceOrientation还可以检测面朝下和面朝上的方向,这意味着如果您的设备位于几乎平坦但略微不平坦的表面上(例如,在突出部分上摇摆),则代码可以在纵向和横向之间随机跳转。 6 / 6s的摄影机)
liamnichols

4
如果电话禁用了自动旋转,则此方法无效。
chengsam '16

178
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    }
    if UIDevice.current.orientation.isFlat {
        print("Flat")
    } else {
        print("Portrait")
    }
}

71
在旋转之前将其称为。如果例如在旋转后需要框架尺寸,则此解决方案将不起作用。
Upvote 2015年

在扩展中不起作用。风景或肖像仍打印“肖像”
TomSawyer

4
如果电话禁用了自动旋转,则此方法无效。
chengsam '16

5
在iPad上,由于大小类在横向和纵向上都是相同的常规,因此永远不会调用此方法。
Jacky


57

需要在将相机与配合使用时检测旋转AVFoundation,并发现didRotate现在不建议使用)&willTransition方法不符合我的需求。使用David发布的通知确实可以,但是对于Swift 3.x / 4.x来说不是最新的。

Swift 4.2 通知名称已更改。

关闭值与Swift 4.0相同:

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

要为Swift 4.2设置通知,请执行以下操作

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

拆除Swift 4.2的通知

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

关于弃用声明,我的最初评论具有误导性,因此我想对其进行更新。如前所述,@objc推论的用法已被弃用,而使用推论则需要#selector。通过使用闭包,可以避免这种情况,并且您现在有了一个解决方案,可以避免由于调用无效的选择器而导致崩溃。

从XCode 10和iOS 4.2开始,下面的所有内容都已过时

Swift 4.0 在Swift 4.0中,Apple鼓励我们避免使用#selector,因此该方法现在使用了完成块。该方法还与Swift 3.x向后兼容,并且是以后推荐的方法。

这是编译器警告,如果您#selector由于@objc推理的弃用而使用该函数,则会在Swift 4.x项目中收到该警告:

在此处输入图片说明

关于这一变化,我们迅速进入

设置回调:

// If you do not use the notification var in your callback, 
// you can safely replace it with _
    var didRotate: (Notification) -> Void = { notification in
        switch UIDevice.current.orientation {
        case .landscapeLeft, .landscapeRight:
            print("landscape")
        case .portrait, .portraitUpsideDown:
            print("Portrait")
        default:
            print("other")
        }
    }

设置通知:

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

撕下:

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

4
您是否提到苹果在不鼓励在Swift 4中使用#selector的地方?我想阅读他们为什么这么说。
jeffjv

@jeffjv当然,我没有直接链接到Apple文档的信息,但是我确实提供了编译器的屏幕快照,警告如果您使用以前的方法,XCode会提供该警告。
CodeBender

1
我已添加了一个链接,它讨论了更改的迅速演变。
CodeBender

1
@CodeBender:编译器警告并不代表您的建议。#selector不会被贬值,只有“ @objc”推断。这意味着当将函数用作#selector时,您必须显式标记它,以便编译器将生成其他正确的代码,因为编译器将不再尝试从您的用法中推断出它。因此,如果在Swift 3.0解决方案中将“ @obj”添加到您的Rotate()函数中,则代码将在没有警告的情况下进行编译。
Mythlandia '18

感谢@Mythlandia,我更新了答案以解决我最初声明中的困惑。
CodeBender

19

使用-orientationof的属性UIDevice是不正确的(即使在大多数情况下也可以使用),并且可能导致某些错误,例如UIDeviceOrientation,如果设备面向上或朝下,也要考虑设备的方向,对于这些设备,UIInterfaceOrientation枚举中没有直接配对价值观。
此外,如果您将应用锁定为某个特定方向,则UIDevice会为您提供设备方向,而无需考虑这一点。
另一方面,iOS8已弃用该类的interfaceOrientation属性UIViewController
有2个选项可用于检测界面方向:

  • 使用状态栏方向
  • 在iPhone上使用大小类(如果未覆盖),它们可以为您提供了解当前界面方向的方式

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

此处提出的解决方案已部分实施,此处提供更多信息:

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

11

我知道这个问题是针对的Swift,但由于它是Google搜索的热门链接之一,并且如果您要在中查找相同的代码,则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

容易,这可以在iOS8和9 / Swift 2 / 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


9

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

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
    }

8

在目标C中

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

迅速

func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

覆盖此方法以检测方向变化。


8

雨燕3 | 经常观察UIDeviceOrientationDidChange通知

每当您的设备在3D空间中更改方向时,以下代码都会打印“ deviceDidRotate”-不管从纵向更改为横向。例如,如果您将手机纵向放置并向前和向后倾斜,则会重复调用deviceDidRotate()。

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

要解决此问题,您可以保持先前的设备方向,并检查deviceDidRotate()中的更改。

var previousDeviceOrientation: UIDeviceOrientation = UIDevice.current.orientation

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIDeviceOrientationDidChange, 
        object: nil
    )
}

func deviceDidRotate() {
    if UIDevice.current.orientation == previousDeviceOrientation { return }
    previousDeviceOrientation = UIDevice.current.orientation
    print("deviceDidRotate")
}

或者,您可以使用其他通知,该通知仅在设备从横向更改为纵向时才被调用。在这种情况下,您想使用UIApplicationDidChangeStatusBarOrientation通知。

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self, 
        selector:  #selector(deviceDidRotate), 
        name: .UIApplicationDidChangeStatusBarOrientation, 
        object: nil
    )
}

func deviceDidRotate() {
    print("deviceDidRotate")
}

6

Swift 3.0中如何检测方向变化的完整工作实现。

我选择使用,因为手机方位的这种实施face upface down分别对我很重要,我想只有一次,我知道方向在指定位置的视图改变。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        //1
        NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

    }

    deinit {
        //3
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    }

    func deviceOrientationDidChange() {
        //2
        switch UIDevice.current.orientation {
        case .faceDown:
            print("Face down")
        case .faceUp:
            print("Face up")
        case .unknown:
            print("Unknown")
        case .landscapeLeft:
            print("Landscape left")
        case .landscapeRight:
            print("Landscape right")
        case .portrait:
            print("Portrait")
        case .portraitUpsideDown:
            print("Portrait upside down")
        }
    }

}

要注意的重要部分是:

  1. 您侦听DeviceOrientationDidChange通知流并将其绑定到功能deviceOrientationDidChange
  2. 然后打开设备方向,请确保unknown有时会注意到方向。
  3. 与任何通知一样,在取消初始化viewController之前,请确保停止观察通知流。

希望有人觉得这有帮助。


谢谢你,伟大的
穆罕默德Razipour

所以我很困惑,当前我的所有视图都是基于纵向调整为横向的,但是如果设备正面朝上,它不会改变吗?当上面的代码面朝上时,我将如何使用您的上述代码来实现相同的效果?
Famic Tech

您能再给些背景吗?我不确定您指的是什么效果。该代码仅用于检测方向。如果您使用多种方法来检测方向,则可能会遇到麻烦。
罗布·诺巴克

6
override func didRotate(from fromInterfaceOrientation: UIInterfaceOrientation) {
    //swift 3
    getScreenSize()
}


func getScreenSize(){
   let screenWidth = UIScreen.main.bounds.width
   let  screenHeight = UIScreen.main.bounds.height
    print("SCREEN RESOLUTION: \(screenWidth.description) x \(screenHeight.description)")
}

最干净的答案。为我工作。
多拉德(Dorad)

现在已弃用
Aziz Javed

5

如果您想在旋转完成后做一些事情,可以使用UIViewControllerTransitionCoordinator像这样的完成处理程序

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

    // Hook in to the rotation animation completion handler
    coordinator.animate(alongsideTransition: nil) { (_) in
        // Updates to your UI...
        self.tableView.reloadData()
    }
}

5

从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`
    })
}

4

检查旋转是否已更改: viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)

使用,coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext)您可以检查转换是否完成。

参见下面的代码:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {

    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition(nil) { (UIViewControllerTransitionCoordinatorContext) in
        // if you want to execute code after transition finished
        print("Transition finished")
    }

    if size.height < size.width {
        // Landscape
        print("Landscape")
    } else {
        // Portrait
        print("Portrait")
    }

}

4

这是检测设备方向的简单方法:(Swift 3

override func willRotate(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) {
            handleViewRotaion(orientation: toInterfaceOrientation)
        }

    //MARK: - Rotation controls
    func handleViewRotaion(orientation:UIInterfaceOrientation) -> Void {
        switch orientation {
        case .portrait :
            print("portrait view")
            break
        case .portraitUpsideDown :
            print("portraitUpsideDown view")
            break
        case .landscapeLeft :
            print("landscapeLeft view")
            break
        case .landscapeRight :
            print("landscapeRight view")
            break
        case .unknown :
            break
        }
    }

2
willRotate现在不推荐使用,更好地使用viewWillTransition

4

斯威夫特4:

override func viewWillAppear(_ animated: Bool) {
    NotificationCenter.default.addObserver(self, selector: #selector(deviceRotated), name: UIDevice.orientationDidChangeNotification, object: nil)
}

override func viewWillDisappear(_ animated: Bool) {
    NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
}

@objc func deviceRotated(){
    if UIDevice.current.orientation.isLandscape {
        //Code here
    } else {
        //Code here
    }
}

当需要跨各种视图控制器进行检测时,很多答案都无济于事。这一个窍门。


您还需要检查在视图控制器消失时是否旋转了设备(如果在当前位置以上打开了其他视图控制器)。事实上,你可以跳过viewWillDisappear,并添加addObserverviewDidLoad。iOS自动取消订阅视图控制器。
亚历山大·沃尔科夫

您忘记了UIDevice.current.orientation.isFlat
user924 '19

3

我的方法类似于bpedit上面显示的方法,但重点是iOS 9+。当视图旋转时,我想更改FSCalendar的范围。

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.setScope(.Week, animated: true)
            self.calendar.appearance.cellShape = .Rectangle
        }
        else {
            self.calendar.appearance.cellShape = .Circle
            self.calendar.setScope(.Month, animated: true)

        }

        }, completion: nil)
}

这下面的工作,但我对此感到毛骨悚然:)

coordinator.animateAlongsideTransition({ (context) in
        if size.height < size.width {
            self.calendar.scope = .Week
            self.calendar.appearance.cellShape = .Rectangle
        }
        }) { (context) in
            if size.height > size.width {
                self.calendar.scope = .Month
                self.calendar.appearance.cellShape = .Circle
            }
    }

2

我通常UIUserInterfaceSizeClass会检测UIViewController类中方向的变化,就像这样:

override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {

    let isiPadLandscapePortrait = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .regular
    let isiPhonePlustLandscape = newCollection.horizontalSizeClass == .regular && newCollection.verticalSizeClass == .compact
    let isiPhonePortrait = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .regular
    let isiPhoneLandscape = newCollection.horizontalSizeClass == .compact && newCollection.verticalSizeClass == .compact

     if isiPhonePortrait {
         // do something...
     }
}

1

对于Swift 3

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.current.orientation.isLandscape {
        //Landscape
    }
    else if UIDevice.current.orientation.isFlat {
        //isFlat
    }
    else {
        //Portrait
    }
}

您忘记了UIDevice.current.orientation.isFlat
user924 '19

1

斯威夫特5

设置类,用于接收设备方向更改的通知:

class MyClass {

    ...

    init (...) {

        ...

        super.init(...)

        // subscribe to device orientation change notifications
        UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil)

        ...

    }

    ...

}

设置处理程序代码:

@objc extension MyClass {
    func orientationChanged(_ notification: NSNotification) {
        let device = notification.object as! UIDevice
        let deviceOrientation = device.orientation

        switch deviceOrientation {
        case .landscapeLeft:   //do something for landscape left
        case .landscapeRight:  //do something for landscape right
        case .portrait:        //do something for portrait
        case .portraitUpsideDown: //do something for portrait upside-down 
        case .faceDown:        //do something for face down
        case .faceUp:          //do something for face up
        case .unknown:         //handle unknown
        @unknown default:      //handle unknown default
        }
    }
}

0
- (void)viewDidLoad {
  [super viewDidLoad];
  [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(OrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}

-(void)OrientationDidChange:(NSNotification*)notification {
  UIDeviceOrientation Orientation=[[UIDevice currentDevice]orientation];

  if(Orientation==UIDeviceOrientationLandscapeLeft || Orientation==UIDeviceOrientationLandscapeRight) {
    NSLog(@"Landscape");
  } else if(Orientation==UIDeviceOrientationPortrait) {
    NSLog(@"Potrait Mode");
  }
}

注意:只需使用此代码来识别UIViewController处于哪个方向


您的代码在项目中不起作用。我还需要做其他事情吗?
Syed Ali Salman

0
override func viewDidLoad() {
    NotificationCenter.default.addObserver(self, selector: #selector(MyController.rotated), name: UIDevice.orientationDidChangeNotification, object: nil)
//...
}

@objc
private func rotated() {
    if UIDevice.current.orientation.isLandscape {

    } else if UIDevice.current.orientation.isPortrait {

    }

    //or you can check orientation separately UIDevice.current.orientation
    //portrait, portraitUpsideDown, landscapeLeft, landscapeRight... 

}

0

在iOS 13.1.2中,方向始终返回0,直到旋转设备为止。我需要在发生任何旋转事件之前调用UIDevice.current.beginGeneratingDeviceOrientationNotifications()以获得实际旋转。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.