簡體   English   中英

iOS16+ 橫向顯示 UIViewController 僅適用於單屏不工作 [Swift 5.7]

[英]iOS16+ Present UIViewController in landscape only for single screen not working [Swift 5.7]

在 iOS 之前 16 橫向呈現單個屏幕適合縱向應用。 工作代碼如下。

備注整個應用程序僅處於縱向模式。

override public var shouldAutorotate: Bool {
    return false
}

override public var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .landscapeLeft
}

override public var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
    return .landscapeLeft
}

我找到了解決方案,但它適用於UIWindowScene ,但我需要 UIWindow 中的解決方案。 我需要幫助在 iOS 16 中修復它。

Xcode - 14.0, iOS - 16.0, 模擬器 - 14 Pro

如果有人需要,我可以准備演示。

我在 iOS 16 Release Notes 中找到了一些相關的東西。
https://developer.apple.com/documentation/ios-ipados-release-notes/ios-16-release-notes?changes=lat__8_1
UIKit 中有一些棄用:

棄用
[UIViewController shouldAutorotate] 已棄用,不再支持。 [UIViewController attemptRotationToDeviceOrientation] 已被棄用並替換為 [UIViewController setNeedsUpdateOfSupportedInterfaceOrientations]。
解決方法:依賴 shouldAutorotate 的應用程序應該使用視圖控制器supportedInterfaceOrientations 反映他們的偏好。 如果支持的方向發生變化,請使用 `-[UIViewController setNeedsUpdateOfSupportedInterface

我認為您可能必須使用setNeedsUpdateOfSupportedInterface

這可以通過兩種方式完成。

1.我個人更喜歡這種方式

1.1 在 AppDelegate 中保留這個 function 來處理方向(這是必須的)

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return .all
}

1.2 您希望在哪個 ViewController 中強制方向,go 到該視圖 controller 並將這些行添加到變量聲明部分

var forceLandscape: Bool = false

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
           forceLandscape ? .landscape : .portrait
}

我們將更新forceLandscape使其得到更新,然后supportedInterfaceOrientations也將得到更新

1.3 這里我們設置了更新forceLandscape的觸發器(我們可以在按鈕動作中添加這幾行代碼來處理IOS 16 force roatation)

if #available(iOS 16.0, *) {
            self.forceLandscape = true
            guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
            self.setNeedsUpdateOfSupportedInterfaceOrientations()
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
                windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight)){
                        error in
                        print(error)
                        print(windowScene.effectiveGeometry)
                }
            })

這將更新forceLandscape ,因此它將檢查方向並根據它進行更新

上面的代碼行是一種方式,下面給出另一種方式

2. 另一種方法是在 AppDelegate class 中更新方向:

2.1 保留這個 function 和 AppDelegate 中的屬性來處理方向(這是必須的)

var orientation : UIInterfaceOrientationMask = .portrait
        func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
            return orientation
        }

2.2 在 viewcontroller 按鈕動作中我們可以更新屬性

@IBAction func buttonAction(_ sender: Any) {
        let appDel = UIApplication.shared.delegate as! AppDelegate
        appDel.orientation = .landscape

        if #available(iOS 16.0, *) {
            DispatchQueue.main.async {
                let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
                self.setNeedsUpdateOfSupportedInterfaceOrientations()
                self.navigationController?.setNeedsUpdateOfSupportedInterfaceOrientations()
                windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscape)) { error in
                    print(error)
                    print(windowScene?.effectiveGeometry ?? "")
                }
            }
        }else{
            UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
        }

我設法從橫向中縱向呈現模態視圖 controller 的最佳解決方法是setNeedsUpdateOfSupportedInterfaceOrientations()requestGeometryUpdate(.iOS(interfaceOrientations: .landscape))新 Z05B8C74CBD96FBF2DE4C1A352F4 上 AppDelegate 上允許的界面方向的組合

應用委托:

var allowedOrientation: UIInterfaceOrientationMask = .allButUpsideDown

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return allowedOrientation
}

以橫向呈現視圖 controller

var overlayWindow: UIWindow? // New window to present the controller in
…

func presentModalInLandscape(vc: ViewController) {
    if #available(iOS 16.0, *) {
        let appdelegate = UIApplication.shared.delegate as! AppDelegate
        appDelegate.allowedOrientation = .landscapeRight

        if let currentWindowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
            overlayWindow = UIWindow(windowScene: currentWindowScene)
        }

        overlayWindow?.windowLevel = UIWindow.Level.alert
        overlayWindow?.rootViewController = livevc

        overlayWindow?.makeKeyAndVisible()
        // It's important to do it after the interface has enough time to rotate
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
           self.overlayWindow?.windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: .landscapeRight))
           vc.setNeedsUpdateOfSupportedInterfaceOrientations()
        }

    } else {
        // For iOS 15 it's enough to present it modally
        self.present(vc, animated: true, completion: nil)
    }
}

然后當你想解雇它時,你需要


if #available(iOS 16.0, *) {
    self.overlayWindow?.isHidden = true // Destroy the window
    self.overlayWindow?.windowScene = nil
    self.overlayWindow = nil 
    appDelegate().allowedOrientation = .allButUpsideDown // Reset allowed orientation
    self.setNeedsUpdateOfSupportedInterfaceOrientations() // Set the controller back
} else {
    self.presentedViewController?.dismiss(animated: true)
}

它仍然不是 100%,因為視圖 controller 以橫向呈現,然后輕彈回縱向,然后在一秒鍾后再次旋轉到橫向。 但如果沒有 UIWindow,它有時會在鎖定橫向模式之前執行 2 倍。

經過多次嘗試,我想出了一個簡單的解決方案。 正如我在問題中提到的,我的整個應用程序僅處於縱向模式,並且我只想在橫向模式下顯示一個屏幕。

此代碼不需要任何外部 window 即可成為makeKeyAndVisible 如果您使用額外的 window 出示,則您需要單獨寫信解散 iOS 16。

在以前版本的 iOS 16 中工作的舊代碼將保持不變,沒有任何變化。

魔術線如下。

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if let _ = window?.rootViewController?.presentedViewController as? LandscapeChartVC {
        if #available(iOS 16.0, *) {
            return .landscapeLeft
        } else {
            return .portrait
        }
    } else {
        return .portrait
    }
}

我在 appDelegate 的supportedInterfaceOrientationsFor中確定了我的橫向視圖 controller。

好吧,您可以更改 word presentedViewController以獲取您的 controller。僅此而已。

使用 iPad 添加對所有 3 或 4 方向的支持:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if UIDevice.IS_IPAD {
        return .allButUpsideDown
    } else {
        if let _ = window?.rootViewController?.presentedViewController as? LandscapeChartVC {
            if #available(iOS 16.0, *) {
                return .landscapeLeft
            } else {
                return .portrait
            }
        } else {
            return .portrait
        }
    }

如果要求 iPad 應用程序鎖定方向,您可以按照 iPhone/上面的代碼。

這個想法提出了答案,並感謝所有有興趣的人。 如果有人仍然得到更多改進的解決方案,我會很樂意更新。

我要鎖定的ViewController位於UINaviationController內。 對於這種情況,這是我的工作解決方案

我有這個struct ,它有一個lockunlock方法。


struct AppOrientation {
    
    // statusBarOrientation
    // 0 - unknown
    // 1 - portrait
    // 2 - portraitUpsideDown
    // 3 - landscapeLeft
    // 4 - landscapeRight
    
    
    static let ORIENTATION_KEY: String = "orientation"
    
    private static func lockInterfaceOrientation(_ orientation: UIInterfaceOrientationMask) {
        
        if let delegate = UIApplication.shared.delegate as? AppDelegate {
            delegate.orientationLock = orientation
        }
    }
        
//    Important
//    Notice that UIDeviceOrientation.landscapeRight is assigned to UIInterfaceOrientation.landscapeLeft
//    and UIDeviceOrientation.landscapeLeft is assigned to UIInterfaceOrientation.landscapeRight.
//    The reason for this is that rotating the device requires rotating the content in the opposite direction.
    
    // Note that UIInterfaceOrientationLandscapeLeft is equal to UIDeviceOrientationLandscapeRight (and vice versa).
    // This is because rotating the device to the left requires rotating the content to the right.
    
    // DevieOrientation
    // 0 - unknown
    // 1 - portrait
    // 2 - portraitUpsideDown
    // 3 - landscapeLeft
    // 4 - landscapeRight
    // 5 - faceUp
    // 6 - faceDown
    
    // UIInterfaceOrientation
    // - landscapeLeft:
    // -- Home button on the left
    // - landscapeRight:
    // -- Home button on the right
    
    // UIDevice orientation
    // - landscapeLeft:
    // -- home button on the right
    // - landscapeRight:
    // -- home button on the left
    
    static func lockDeviceToLandscapeLeft() {
        
        if #available(iOS 16.0, *) {
            let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
            
            if let controller = windowScene?.keyWindow?.rootViewController as? RMNavigationController {
                controller.lockLandscape = true
            }
        } else {
            UIDevice.current.setValue(UIDeviceOrientation.landscapeLeft.rawValue, forKey: ORIENTATION_KEY)
            lockInterfaceOrientation(.landscapeRight)
        }
        

        
        // the docs say you should call 'attemptRorationToDeviceOrientation()
        // lots of StackOverflow answers don't use it,
        // but a couple say you _must_ call it.
        // for me, not calling it seems to work...
//        UINavigationController.attemptRotationToDeviceOrientation()
    }
    
    static func unlockOrientation() {
        if #available(iOS 16.0, *) {
            let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
            
            if let controller = windowScene?.keyWindow?.rootViewController as? RMNavigationController {
                controller.lockLandscape = false
            }
        } else {
            lockInterfaceOrientation(.all)
        }
    }
}

在 ios16 之前你只需要調用這兩個方法。

從 ios16 開始,您現在還需要調用setNeedsUpdateOfSupportedInterfaceOrientations()


override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
  AppOrientation.lockDeviceToLandscapeLeft()
        
  if #available(iOS 16.0, *) {
    self.setNeedsUpdateOfSupportedInterfaceOrientations()
  } else {
    // Fallback on earlier versions
  }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    AppOrientation.unlockOrientation()
}

暫無
暫無

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

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