簡體   English   中英

Swift - 如何檢測方向變化

[英]Swift - How to detect orientation changes

我想將兩個圖像添加到單個圖像視圖(即橫向一個圖像和縱向另一個圖像),但我不知道如何使用 swift 語言檢測方向變化。

我試過這個答案,但它只需要一張圖片

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

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

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

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 5.* iOS14 及以下

你真的應該區分:

  • Device Orientation => 表示物理設備的方向
  • Interface Orientation => 表示界面顯示在屏幕上的方向

這兩個值不匹配的情況有很多,例如:

  • 當您鎖定屏幕方向時
  • 當您將設備放平時

在大多數情況下,您會希望使用界面方向,並且可以通過窗口獲取它:

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

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

如果我們需要重新加載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)

如果您使用的是 Swift 版本 >= 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,請不要忘記在右側面板的 Identity Inspector 部分中為此 UIStackView 對象選擇自定義類。 然后只需創建(也通過 Interface Builder)對自定義 UIStackView 實例的 IBOutlet 引用:

@IBOutlet weak var stackView: MyStackView!

采納這個想法並使其適應您的需求。 希望這可以幫助你!

我相信正確的答案實際上是兩種方法的組合: viewWIllTransition(toSize:)NotificationCenterUIDeviceOrientationDidChange

viewWillTransition(toSize:)在過渡之前通知您。

NotificationCenter UIDeviceOrientationDidChange之后通知您。

你必須非常小心。 例如,在UISplitViewController當設備旋轉到特定方向時, DetailViewController會從UISplitViewControllerviewcontrollers數組中彈出,並推送到 master 的UINavigationController 如果您在旋轉完成之前搜索詳細視圖控制器,它可能不存在並崩潰。

斯威夫特 4

我在使用UIDevice.current.orientation更新 ViewControllers 視圖時遇到了一些小問題,例如在子視圖的旋轉或動畫期間更新 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)

您可以使用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)總是在viewWillTransition(to size: CGSize)之后調用更新的方向。

以前的所有貢獻都很好,但有一點要注意:

a) 如果在 plist 中設置了方向,只有縱向或示例,您將不會通過 viewWillTransition 收到通知

b) 如果我們無論如何需要知道用戶是否旋轉了設備,(例如游戲或類似的......),我們只能使用:

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

在 Xcode8、iOS11 上測試

要在應用程序啟動時獲得正確的方向,您必須在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()
}

這將始終有效,在應用程序首次啟動時,如果在應用程序運行時旋轉。

這是一個現代的組合解決方案:

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

另一種檢測設備方向的方法是使用 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)
    //...
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM