Swift-如何检测方向变化


93

我想将两个图像添加到单个图像视图中(即用于横向显示一个图像,用于纵向显示另一个图像),但是我不知道如何使用快速语言检测方向变化。

我尝试了这个答案,但只拍了一张图片

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    if UIDevice.currentDevice().orientation.isLandscape.boolValue {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

我是iOS开发的新手,任何建议将不胜感激!


1
您能否编辑问题并设置代码格式?选择代码后,可以使用cmd + k进行操作。
jbehrens94 '16


是否正确打印风景/肖像?
丹尼尔(Daniel)

是的,它可以正确打印@simpleBob
Raghuram,

您应该使用界面方向而非设备方向=> stackoverflow.com/a/60577486/8780127
Wilfried Josset

Answers:


120
let const = "Background" //image name
let const2 = "GreyBackground" // image name
    @IBOutlet weak var imageView: UIImageView!
    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.image = UIImage(named: const)
        // Do any additional setup after loading the view.
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        if UIDevice.current.orientation.isLandscape {
            print("Landscape")
            imageView.image = UIImage(named: const2)
        } else {
            print("Portrait")
            imageView.image = UIImage(named: const)
        }
    }

谢谢,它适用于imageView,如果我想向viewController添加背景图像该怎么办?
拉古拉姆

1
将imageView放在背景上。为imageView的顶部,底部,前导,后方约束赋予ViewController主视图以覆盖所有背景。
Rutvik Kanbargi '16

4
不要忘记在您的覆盖方法中调用super.viewWillTransitionToSize
Brian Sachetta

您知道为什么viewWillTransition子类化时不能覆盖UIView吗?
Xcoder

2
还有另一种方向类型:.isFlat。如果您只想捕获portrait,建议您将else子句更改为else if UIDevice.current.orientation.isPortrait。注意:.isFlat可以同时发生在肖像和风景中,仅使用{}时,它将始终默认存在(即使它是平坦的风景)。
PMT

76

使用NotificationCenterUIDevicebeginGeneratingDeviceOrientationNotifications

迅捷4.2+

override func viewDidLoad() {
    super.viewDidLoad()        

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

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

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

迅捷3

override func viewDidLoad() {
    super.viewDidLoad()        

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}

deinit {
     NotificationCenter.default.removeObserver(self)
}

func rotated() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

1
绝对-我的方法需要一些其他代码。但是它确实“检测到方向改变”。
maslovsa

3
@Michael因为viewWillTransition:期望更改完成后会UIDeviceOrientationDidChange调用使用时发生更改。
林德西·斯科特

1
@LyndseyScott我仍然认为,无需使用潜在危险的NSNotificationCenter即可实现所描述的行为。请查看以下答案,并让我知道您的想法:stackoverflow.com/a/26944087/1306884
Michael

4
这个答案有趣的是,即使将视图控制器的shouldAutorotate设置为,这也将给出当前方向false。这对于“相机”应用程序的主屏幕等屏幕非常方便-方向已锁定,但按钮可以旋转。
yoninja

1
从iOS 9开始,您甚至无需显式调用deinit。:你可以阅读更多关于它在这里useyourloaf.com/blog/...
詹姆斯乔丹·泰勒

63

Swift 3 以上代码已更新:

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

    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
    } else {
        print("Portrait")
    }
}

1
您知道为什么在子类化UIView时不能覆盖viewWillTransition吗?
Xcoder

如果您尝试引用控制器中的任何视图,则会崩溃。
詹姆斯·乔丹·泰勒

@JamesJordanTaylor您能解释一下为什么它会崩溃吗?
Orium

我是在6个月前发表评论的,但是我认为Xcode尝试使用它的原因是nil指针异常,因为视图本身尚未实例化,仅是viewController。我可能记错了。
詹姆斯·乔丹·泰勒

@Xcoder这是UIViewController而非UIView上的函数-这就是为什么您不能覆盖它的原因。
LightningStryk

23

Device️设备方向!=接口方向⚠️

Swift 5. * iOS13及以下

您应该真正在以下方面有所作为:

  • 设备方向=>指示物理设备的方向
  • 界面方向=>指示屏幕上显示的界面的方向

在许多情况下,这两个值不匹配,例如:

  • 锁定屏幕方向时
  • 将设备放平时

在大多数情况下,您可能希望使用界面方向,并且可以通过窗口获取它:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
}

如果您还想支持<iOS 13(例如iOS 12),则可以执行以下操作:

private var windowInterfaceOrientation: UIInterfaceOrientation? {
    if #available(iOS 13.0, *) {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    } else {
        return UIApplication.shared.statusBarOrientation
    }
}

现在,您需要定义对窗口界面方向变化做出反应的位置。有多种方法可以做到这一点,但最佳解决方案是在内完成 willTransition(to newCollection: UITraitCollection

每次更改界面方向时,都会触发此继承的UIViewController方法。因此,您可以在后者中进行所有修改。

这是一个解决方案示例:

class ViewController: UIViewController {
    override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
        super.willTransition(to: newCollection, with: coordinator)

        coordinator.animate(alongsideTransition: { (context) in
            guard let windowInterfaceOrientation = self.windowInterfaceOrientation else { return }

            if windowInterfaceOrientation.isLandscape {
                // activate landscape changes
            } else {
                // activate portrait changes
            }
        })
    }

    private var windowInterfaceOrientation: UIInterfaceOrientation? {
        return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
    }
}

通过实现此方法,您将能够对界面方向的任何更改做出反应。但是请记住,它不会在应用程序打开时触发,因此您还必须在中手动更新界面viewWillAppear()

我创建了一个示例项目,强调了设备方向和接口方向之间的区别。此外,它将帮助您了解不同的行为,具体取决于您决定更新UI的生命周期步骤。

随时克隆并运行以下存储库:https : //github.com/wjosset/ReactToOrientation


在iOS 13之前该怎么做?
Daniel Springer

1
@DanielSpringer谢谢您的评论,我已编辑该帖子以支持iOS 12及以下版本
Wilfried Josset

1
我发现这个答案是最好,最有用的。但是在iPadOS13.5上使用建议的测试func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)无法正常工作。我已经使用进行了工作func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
安德烈

12

Swift 4+: 我正在使用它进行软键盘设计,由于某种原因,该UIDevice.current.orientation.isLandscape方法一直告诉我它是Portrait,所以这是我所使用的:

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

    if(size.width > self.view.frame.size.width){
        //Landscape
    }
    else{
        //Portrait
    }
}

好吧,我的iMessage应用程序中确实存在类似的问题。真的很奇怪吗?而且,即使您的解决方案也相反!¯_(ツ)_ /¯–
Shyam

不要使用UIScreen.main.bounds,因为它可能会给您过渡之前的屏幕边界(注意方法中的“ Will”),尤其是在多次快速旋转时。您应该使用'size'参数...
Dan Bodnar

@ dan-bodnar您是指nativeBounds参数吗?
asetniop

3
@asetniop,不。size在上面编写的viewWillTransitionToSize:withCoordinator:方法中注入的参数。这将反映过渡后视图的确切大小。
Dan Bodnar '18

如果您使用子视图控制器,这不是一个好的解决方案,容器的大小可能始终为正方形,而与方向无关。
bojan

8

Swift 4.2,RxSwift

如果需要重新加载collectionView。

NotificationCenter.default.rx.notification(UIDevice.orientationDidChangeNotification)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

迅捷4,RxSwift

如果需要重新加载collectionView。

NotificationCenter.default.rx.notification(NSNotification.Name.UIDeviceOrientationDidChange)
    .observeOn(MainScheduler.instance)
    .map { _ in }            
    .bind(to: collectionView.rx.reloadData)
    .disposed(by: bag)

5

如果您使用的是Swift版本> = 3.0,则必须执行某些代码更新,就像其他人已经说过的那样。只是不要忘了称呼super:

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

   super.viewWillTransition(to: size, with: coordinator)

   // YOUR CODE OR FUNCTIONS CALL HERE

}

如果您想对图像使用StackView,请注意可以执行以下操作:

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

   super.viewWillTransition(to: size, with: coordinator)

   if UIDevice.current.orientation.isLandscape {

      stackView.axis = .horizontal

   } else {

      stackView.axis = .vertical

   } // else

}

如果您使用的是Interface Builder,请不要忘记在右侧面板的Identity Inspector部分中为此UIStackView对象选择自定义类。然后只需(也通过Interface Builder)创建对自定义UIStackView实例的IBOutlet引用:

@IBOutlet weak var stackView: MyStackView!

采纳想法并使其适应您的需求。希望这可以帮到你!


关于调用超级的好点。不要在重写的函数中调用super方法确实是草率的编码,它可能会在应用程序中产生不可预测的行为。还有可能很难发现的潜在错误!
亚当·弗里曼

您知道为什么在子类化UIView时不能覆盖viewWillTransition吗?
Xcoder的

@Xcoder(通常)对象未应用替代的原因在于运行时实例不是使用自定义类创建的,而是使用默认类创建的。如果使用的是Interface Builder,请确保在右侧面板的Identity Inspector部分中为此UIStackView对象选择自定义类。
WiseRatel

5

斯威夫特4

使用来更新ViewControllers视图时,我遇到了一些小问题UIDevice.current.orientation,例如在旋转或子视图动画期间更新表视图单元的约束。

代替上述方法,我目前正在将过渡大小与视图控制器的视图大小进行比较。这似乎是正确的方法,因为此时代码中可以访问两者:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    print("Will Transition to size \(size) from super view size \(self.view.frame.size)")

    if (size.width > self.view.frame.size.width) {
        print("Landscape")
    } else {
        print("Portrait")
    }

    if (size.width != self.view.frame.size.width) {
        // Reload TableView to update cell's constraints.
    // Ensuring no dequeued cells have old constraints.
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
    }


}

在iPhone 6上输出:

Will Transition to size (667.0, 375.0) from super view size (375.0, 667.0) 
Will Transition to size (375.0, 667.0) from super view size (667.0, 375.0)

我是否必须从项目级别启用定向支持?
Ericpoon

4

我认为正确的答案实际上是两者的结合方法:viewWIllTransition(toSize:)NotificationCenterUIDeviceOrientationDidChange

viewWillTransition(toSize:)过渡通知您。

NotificationCenter UIDeviceOrientationDidChange通知你之后

你要非常小心 例如,UISplitViewController当设备旋转到特定方向时,DetailViewController获取的对象将从UISplitViewControllerviewcontrollers数组中弹出,然后推到主设备上UINavigationController。如果在旋转完成之前去搜索局部视图控制器,则它可能不存在并崩溃。


3

以前所有的贡献都可以,但请注意:

a)如果在plist中设置了方向(仅纵向或示例),则不会通过viewWillTransition通知您

b)如果我们仍然需要知道用户是否旋转了设备(例如游戏或类似产品),我们只能使用:

NotificationCenter.default.addObserver(self, selector: #selector(ViewController.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)

在Xcode8,iOS11上测试


1

要在应用启动时获得正确的方向,您必须将其签入viewDidLayoutSubviews()。此处描述的其他方法无效。

这是一个示例如何做:

var mFirstStart = true

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if (mFirstStart) {
        mFirstStart = false
        detectOrientation()
    }
}

func detectOrientation() {
    if UIDevice.current.orientation.isLandscape {
        print("Landscape")
        // do your stuff here for landscape
    } else {
        print("Portrait")
        // do your stuff here for portrait
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    detectOrientation()
}

应用首次启动时以及在应用运行时旋转,此方法始终有效。


1

过渡完成后,您可以使用viewWillTransition(to:with:)并点击进入animate(alongsideTransition:completion:)界面方向。您只需定义并实现与此协议类似的协议即可利用该事件。请注意,此代码用于SpriteKit游戏,您的具体实现可能有所不同。

protocol CanReceiveTransitionEvents {
    func viewWillTransition(to size: CGSize)
    func interfaceOrientationChanged(to orientation: UIInterfaceOrientation)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)

        guard
            let skView = self.view as? SKView,
            let canReceiveRotationEvents = skView.scene as? CanReceiveTransitionEvents else { return }

        coordinator.animate(alongsideTransition: nil) { _ in
            if let interfaceOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation {
                canReceiveRotationEvents.interfaceOrientationChanged(to: interfaceOrientation)
            }
        }

        canReceiveRotationEvents.viewWillTransition(to: size)
    }

您可以在这些函数中设置断点,并观察interfaceOrientationChanged(to orientation: UIInterfaceOrientation)始终viewWillTransition(to size: CGSize)在更新方向后调用该断点


0

检测设备方向的另一种方法是使用函数traitCollectionDidChange(_ :)。当iOS界面环境更改时,系统会调用此方法。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
    super.traitCollectionDidChange(previousTraitCollection)
    //...
}

此外,您可以使用willTransition(to:with :)函数(在traitCollectionDidChange(_ :)之前调用)来获取信息,方法是在应用方向之前。

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}
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.