快速-如何检测方向变化

我想添加两个图像到单一图像视图(即景观一个图像和肖像另一个图像) ,但我不知道如何检测方向变化使用快速语言。

我尝试了这个答案,但它只需要一个形象

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

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

158447 次浏览
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)
}
}

使用 通知中心UIDDevicebeginGeneratingDeviceOrientationNotifications

Swift 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

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 以上代码更新:

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

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

以前所有的贡献都很好,但有一点需要注意:

A)如果方向设置在 plist,只有肖像或例子,您将通过 viewWillTransfer 通知 没有

B)我们是否需要知道用户是否有旋转设备(例如游戏或类似的设备)。.)我们只能使用:

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

在 Xcode8和 iOS11上测试

我相信正确的答案实际上是两种方法的结合: viewWIllTransition(toSize:)NotificationCenterUIDeviceOrientationDidChange

viewWillTransition(toSize:)通知您 之前的过渡。

通知你。

你必须非常小心。例如,在 UISplitViewController中,当设备旋转到某个方向时,DetailViewControllerUISplitViewControllerviewcontrollers阵列中弹出,并被推到主 UINavigationController上。如果您在旋转结束之前搜索详细视图控制器,那么它可能不存在并且会崩溃。

如果您使用的是 Swift version > = 3.0,那么有些代码更新必须按照其他人已经说过的那样进行。别忘了打电话给管理员:

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,不要忘记为这个 uistackView 对象选择自定义类,在右边面板的“身份检查器”部分。然后只需创建(也通过 Interface Builder)定制的 UIstackView 实例的 IBoutlet 引用:

@IBOutlet weak var stackView: MyStackView!

接受这个想法,并使之适应你的需要。希望这能帮助你!

为了在 应用程序启动上获得正确的方向,你必须在 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()
}

这将始终工作,对 应用程序首次启动,如果旋转 当应用程序运行时

Swift 4

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

目前我不使用上述方法,而是将转换大小与视图控制器视图大小进行比较。这似乎是正确的做法,因为在代码的这一点上,一个人可以同时访问这两个方面:

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)

Swift 4.2,RxSwift

如果我们需要重新加载 CollectionView。

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

Swift 4,RxSwift

如果我们需要重新加载 CollectionView。

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

您可以使用 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)之后以更新的方向调用。

设备定位! = 界面定位

Swift 5. * iOS16及以下版本

你真的应该区分:

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

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

  • 当你锁定屏幕方向时
  • 当你把你的设备放平时

在大多数情况下,你会希望使用界面方向,你可以通过窗口得到它:

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

如果你也想支持 < iOS13(比如 iOS12) ,你可以这样做:

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

检测设备方向的另一种方法是使用函数 traitCollectionDidChange (_:)。当 iOS 接口环境发生变化时,系统调用此方法。

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

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

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

下面是一个现代的组合解决方案:

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

我的应用程序是在 iOS 15上运行的,我只在 iPhone/iPad 上检查过,所以我不能说所有的用例,但是我使用以下环境变量:

@Environment(\.verticalSizeClass) private var verticalSizeClass

然后使用以下方法检查它的值: verticalSizeClass == .compact是水平的 verticalSizeClass == .regular是垂直的

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