简体   繁体   English

Swift - 如何检测方向变化

[英]Swift - How to detect orientation changes

I want to add two images to single image view (ie for landscape one image and for portrait another image)but i don't know how to detect orientation changes using swift languages.我想将两个图像添加到单个图像视图(即横向一个图像和纵向另一个图像),但我不知道如何使用 swift 语言检测方向变化。

I tried this answer but it takes only one image我试过这个答案,但它只需要一张图片

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

I am new to iOS development,any advice would be greatly appreciated!我是 iOS 开发的新手,任何建议将不胜感激!

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

Using NotificationCenter and UIDevice 's beginGeneratingDeviceOrientationNotifications使用NotificationCenterUIDevicebeginGeneratingDeviceOrientationNotifications

Swift 4.2+斯威夫特 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")
    }
}

Swift 3斯威夫特 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")
    }
}

Swift 3 Above code updated: 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")
    }
}

⚠️Device Orientation != Interface Orientation⚠️ ⚠️设备方向 != 界面方向⚠️

Swift 5.* iOS14 and below Swift 5.* iOS14 及以下

You should really make a difference between:你真的应该区分:

  • Device Orientation => Indicates the orientation of the physical device Device Orientation => 表示物理设备的方向
  • Interface Orientation => Indicates the orientation of the interface displayed on the screen Interface Orientation => 表示界面显示在屏幕上的方向

There are many scenarios where those 2 values are mismatching such as:这两个值不匹配的情况有很多,例如:

  • When you lock your screen orientation当您锁定屏幕方向时
  • When you have your device flat当您将设备放平时

In most cases you would want to use the interface orientation and you can get it via the window:在大多数情况下,您会希望使用界面方向,并且可以通过窗口获取它:

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

In case you also want to support < iOS 13 (such as iOS 12) you would do the following:如果您还想支持 < 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
    }
}

Now you need to define where to react to the window interface orientation change.现在您需要定义对窗口界面方向变化做出反应的位置。 There are multiple ways to do that but the optimal solution is to do it within willTransition(to newCollection: UITraitCollection .有多种方法可以做到这一点,但最佳解决方案是在willTransition(to newCollection: UITraitCollection

This inherited UIViewController method which can be overridden will be trigger every time the interface orientation will be change.这个可以被覆盖的继承的 UIViewController 方法将在每次界面方向改变时触发。 Consequently you can do all your modifications in the latter.因此,您可以在后者中进行所有修改。

Here is a solution example:这是一个解决方案示例:

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

By implementing this method you'll then be able to react to any change of orientation to your interface.通过实施此方法,您将能够对界面的任何方向变化做出反应。 But keep in mind that it won't be triggered at the opening of the app so you will also have to manually update your interface in viewWillAppear() .但请记住,它不会在应用程序打开时触发,因此您还必须在viewWillAppear()手动更新您的界面。

I've created a sample project which underlines the difference between device orientation and interface orientation.我创建了一个示例项目,它强调了设备方向和界面方向之间的区别。 Additionally it will help you to understand the different behavior depending on which lifecycle step you decide to update your UI.此外,它将帮助您了解不同的行为,具体取决于您决定更新 UI 的生命周期步骤。

Feel free to clone and run the following repository: https://github.com/wjosset/ReactToOrientation随意克隆并运行以下存储库: https : //github.com/wjosset/ReactToOrientation

Swift 4+: I was using this for soft keyboard design, and for some reason the UIDevice.current.orientation.isLandscape method kept telling me it was Portrait , so here's what I used instead: 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
    }
}

Swift 4.2, RxSwift Swift 4.2,RxSwift

If we need to reload collectionView.如果我们需要重新加载collectionView。

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

Swift 4, RxSwift Swift 4,RxSwift

If we need to reload collectionView.如果我们需要重新加载collectionView。

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

If your are using Swift version >= 3.0 there are some code updates you have to apply as others have already said.如果您使用的是 Swift 版本 >= 3.0,那么您必须像其他人所说的那样应用一些代码更新。 Just don't forget to call super:只是不要忘记调用超级:

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

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

   // YOUR CODE OR FUNCTIONS CALL HERE

}

If you are thinking to use a StackView for your images be aware you can do something like the following:如果您正在考虑为您的图像使用 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

}

If your are using Interface Builder don't forget to select the custom class for this UIStackView object, in the Identity Inspector section at the right panel.如果您使用的是 Interface Builder,请不要忘记在右侧面板的 Identity Inspector 部分中为此 UIStackView 对象选择自定义类。 Then just create (also through Interface Builder) the IBOutlet reference to the custom UIStackView instance:然后只需创建(也通过 Interface Builder)对自定义 UIStackView 实例的 IBOutlet 引用:

@IBOutlet weak var stackView: MyStackView!

Take the idea and adapt it to your needs.采纳这个想法并使其适应您的需求。 Hope this can help you!希望这可以帮助你!

I believe the correct answer is actually a combination of both approaches: viewWIllTransition(toSize:) and NotificationCenter 's UIDeviceOrientationDidChange .我相信正确的答案实际上是两种方法的组合: viewWIllTransition(toSize:)NotificationCenterUIDeviceOrientationDidChange

viewWillTransition(toSize:) notifies you before the transition. viewWillTransition(toSize:)在过渡之前通知您。

NotificationCenter UIDeviceOrientationDidChange notifies you after . NotificationCenter UIDeviceOrientationDidChange之后通知您。

You have to be very careful.你必须非常小心。 For example, in UISplitViewController when the device rotates into certain orientations, the DetailViewController gets popped off the UISplitViewController 's viewcontrollers array, and pushed onto the master's UINavigationController .例如,在UISplitViewController当设备旋转到特定方向时, DetailViewController会从UISplitViewControllerviewcontrollers数组中弹出,并推送到 master 的UINavigationController If you go searching for the detail view controller before the rotation has finished, it may not exist and crash.如果您在旋转完成之前搜索详细视图控制器,它可能不存在并崩溃。

Swift 4斯威夫特 4

I've had some minor issues when updating the ViewControllers view using UIDevice.current.orientation , such as updating constraints of tableview cells during rotation or animation of subviews.我在使用UIDevice.current.orientation更新 ViewControllers 视图时遇到了一些小问题,例如在子视图的旋转或动画期间更新 tableview 单元格的约束。

Instead of the above methods I am currently comparing the transition size to the view controllers view size.我目前将过渡大小与视图控制器视图大小进行比较,而不是上述方法。 This seems like the proper way to go since one has access to both at this point in code:这似乎是正确的方法,因为此时可以在代码中访问两者:

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


}

Output on a iPhone 6: 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)

You can use viewWillTransition(to:with:) and tap into animate(alongsideTransition:completion:) to get the interface orientation AFTER the transition is complete.您可以使用viewWillTransition(to:with:)并点击animate(alongsideTransition:completion:)以在转换完成后获取界面方向。 You just have to define and implement a protocol similar to this in order to tap into the event.您只需要定义和实现一个与此类似的协议即可利用该事件。 Note that this code was used for a SpriteKit game and your specific implementation may differ.请注意,此代码用于 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)
    }

You can set breakpoints in these functions and observe that interfaceOrientationChanged(to orientation: UIInterfaceOrientation) is always called after viewWillTransition(to size: CGSize) with the updated orientation.您可以在这些函数中设置断点并观察interfaceOrientationChanged(to orientation: UIInterfaceOrientation) viewWillTransition(to size: CGSize)总是在viewWillTransition(to size: CGSize)之后调用更新的方向。

All previous contributes are fine, but a little note:以前的所有贡献都很好,但有一点要注意:

a) if orientation is set in plist, only portrait or example, You will be not notified via viewWillTransition a) 如果在 plist 中设置了方向,只有纵向或示例,您将不会通过 viewWillTransition 收到通知

b) if we anyway need to know if user has rotated device, (for example a game or similar..) we can only use: b) 如果我们无论如何需要知道用户是否旋转了设备,(例如游戏或类似的......),我们只能使用:

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

tested on Xcode8, iOS11在 Xcode8、iOS11 上测试

To get the correct orientation on app start you have to check it in viewDidLayoutSubviews() .要在应用程序启动时获得正确的方向,您必须在viewDidLayoutSubviews()检查它。 Other methods described here won't work.此处描述的其他方法将不起作用。

Here's an example how to do it:以下是如何执行此操作的示例:

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

This will work always, on app first start , and if rotating while the app is running .这将始终有效,在应用程序首次启动时,如果在应用程序运行时旋转。

Here is a modern Combine solution:这是一个现代的组合解决方案:

import UIKit
import Combine

class MyClass: UIViewController {

     private var subscriptions = Set<AnyCancellable>()

     override func viewDidLoad() {
         super.viewDidLoad()
    
         NotificationCenter
             .default
             .publisher(for: UIDevice.orientationDidChangeNotification)
             .sink { [weak self] _ in
            
                 let orientation = UIDevice.current.orientation
                 print("Landscape: \(orientation.isLandscape)")
         }
         .store(in: &subscriptions)
    }
}

My app is running on iOS 15 and I have checked only on iPhone/iPad so I can't say about all use cases however I am using the following environment variable:我的应用程序在 iOS 15 上运行,我只在 iPhone/iPad 上进行了检查,所以我不能说所有用例,但是我使用以下环境变量:

@Environment(\.verticalSizeClass) private var verticalSizeClass

Then checking its value using the following: verticalSizeClass ==.compact is horizontal verticalSizeClass ==.regular is vertical然后使用以下命令检查其值: verticalSizeClass ==.compact是水平的verticalSizeClass ==.regular是垂直的

https://developer.apple.com/documentation/swiftui/environmentvalues/verticalsizeclass https://developer.apple.com/documentation/swiftui/environmentvalues/verticalsizeclass

Another way to detect device orientations is with the function traitCollectionDidChange(_:).另一种检测设备方向的方法是使用 traitCollectionDidChange(_:) 函数。 The system calls this method when the iOS interface environment changes.当iOS界面环境发生变化时,系统会调用该方法。

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

Furthermore, you can use function willTransition(to:with:) ( which is called before traitCollectionDidChange(_:) ), to get information just before the orientation is applied.此外,您可以使用函数 willTransition(to:with:) (在 traitCollectionDidChange(_:) 之前调用),在应用方向之前获取信息。

 override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator)
{
    super.willTransition(to: newCollection, with: coordinator)
    //...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM