只轮换一次ViewController

Rotation only in one ViewController

我正在尝试旋转一个视图,而所有其他视图 (5) 都固定为纵向。原因是在那个视图中我希望用户观看他之前保存的图片。我想这是可能的,但到目前为止我还不知道如何实现。任何人都可以帮助或给我提示吗? 我正在 Swift 运行 iOS8

中编程

在ViewController中使用 shouldAutorotatesupportedInterfaceOrientations 方法来显示横向和纵向模式:

此方法应覆盖故事板设置。

override func shouldAutorotate() -> Bool {
    return true
}

override func supportedInterfaceOrientations() -> Int {
    return UIInterfaceOrientation.Portrait.rawValue | UIInterfaceOrientation.LandscapeLeft.rawValue | UIInterfaceOrientation.LandscapeRight.rawValue
}

我建议在您的 appDelegate 中使用 supportedInterfaceOrientationsForWindow 以仅允许在该特定视图控制器中旋转,例如:

Swift 4/Swift 5

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

    // Make sure the root controller has been set
    // (won't initially be set when the app is launched)
    if let navigationController = self.window?.rootViewController as? UINavigationController {

        // If the visible view controller is the
        // view controller you'd like to rotate, allow
        // that window to support all orientations
        if navigationController.visibleViewController is SpecificViewController {
            return UIInterfaceOrientationMask.all
        } 

        // Else only allow the window to support portrait orientation
        else {
            return UIInterfaceOrientationMask.portrait
        }
    }

    // If the root view controller hasn't been set yet, just
    // return anything
    return UIInterfaceOrientationMask.portrait
}

请注意,如果 SpecificViewController 在进入纵向屏幕之前处于横向状态,则另一个视图仍将以横向方式打开。为了避免这种情况,我建议在该视图处于横向时禁止转换。


Swift 3

func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {

    // Make sure the root controller has been set
    // (won't initially be set when the app is launched)
    if let navigationController = self.window?.rootViewController as? UINavigationController {

        // If the visible view controller is the
        // view controller you'd like to rotate, allow
        // that window to support all orientations
        if navigationController.visibleViewController is SpecificViewController  {
            return Int(UIInterfaceOrientationMask.All.rawValue)
        }

        // Else only allow the window to support portrait orientation
        else {
            return Int(UIInterfaceOrientationMask.Portrait.rawValue)
        }
    }

    // If the root view controller hasn't been set yet, just
    // return anything
    return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}

Swift 3: 添加代码 AppDelegate.swift

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
            if (rootViewController.responds(to: Selector(("canRotate")))) {
                // Unlock landscape view orientations for this view controller
                return .allButUpsideDown;
            }
        }

        // Only allow portrait (standard behaviour)
        return .portrait;
    }

    private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
        if (rootViewController == nil) { return nil }
        if (rootViewController.isKind(of: (UITabBarController).self)) {
            return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
        } else if (rootViewController.isKind(of:(UINavigationController).self)) {
            return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
        } else if (rootViewController.presentedViewController != nil) {
            return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
        }
        return rootViewController
    }

然后:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }


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

        if (self.isMovingFromParentViewController) {
            UIDevice.current.setValue(Int(UIInterfaceOrientation.portrait.rawValue), forKey: "orientation")
        }
    }

    func canRotate() -> Void {}

}

http://www.jairobjunior.com/blog/2016/03/05/how-to-rotate-only-one-view-controller-to-landscape-in-ios-slash-swift/

我刚刚遇到了一个非常相似的问题,我想以纵向和横向模式显示视频播放器,而应用程序的其余部分仅为纵向。我的主要问题是,当我以横向模式关闭视频 vc 时,呈现 vc 只是短暂地处于横向模式。
正如在对 this can be circumvented by disallowing transitions while in landscape mode, but by combining this and this 的评论中指出的那样,我找到了一个更好、更通用的解决方案 (IMO)。此解决方案允许在您放置 canRotate(){} 的所有 vc 中旋转并且不旋转呈现 vc。

Swift 3:
在 AppDelegate.swift:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController) {
        if (rootViewController.responds(to: Selector(("canRotate")))) {
            // Unlock landscape view orientations for this view controller if it is not currently being dismissed
            if !rootViewController.isBeingDismissed{
                return .allButUpsideDown
            }
        }
    }

    // Only allow portrait (standard behaviour)
    return .portrait
}

private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
    if (rootViewController == nil) {
        return nil
    }
    if (rootViewController.isKind(of: UITabBarController.self)) {
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
    } else if (rootViewController.isKind(of: UINavigationController.self)) {
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
    } else if (rootViewController.presentedViewController != nil) {
        return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
    }
    return rootViewController
}

在每个允许旋转的视图控制器中:

func canRotate(){}

您也可以以面向协议的方式进行。 只需创建协议

protocol CanRotate {

}

以更"swifty"的方式在 AppDelegate 中添加相同的 2 个方法

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if topViewController(in: window?.rootViewController) is CanRotate {
        return .allButUpsideDown
    } else {
        return .portrait
    }
}


func topViewController(in rootViewController: UIViewController?) -> UIViewController? {
    guard let rootViewController = rootViewController else {
        return nil
    }

    if let tabBarController = rootViewController as? UITabBarController {
        return topViewController(in: tabBarController.selectedViewController)
    } else if let navigationController = rootViewController as? UINavigationController {
        return topViewController(in: navigationController.visibleViewController)
    } else if let presentedViewController = rootViewController.presentedViewController {
        return topViewController(in: presentedViewController)
    }
    return rootViewController
}

并且在您想要不同行为的每个 ViewController 中,只需在 class 的定义中添加协议名称即可。

class ViewController: UIViewController, CanRotate {}

如果你想要任何特定的组合,你可以在协议中添加一个变量来覆盖

protocol CanRotate {
    var supportedInterfaceOrientations: UIInterfaceOrientationMask
}

SWIFT 4

对于UITabBarController我们可以在AppDelegate.swift中使用这行代码吗?

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    if let tabBarController = window?.rootViewController as? UITabBarController {
        if let tabBarViewControllers = tabBarController.viewControllers {
            if let projectsNavigationController = tabBarViewControllers[1] as? UINavigationController {
                if projectsNavigationController.visibleViewController is PickerViewController //use here your own ViewController class name {
                    return .all
                }
            }
        }
    }
    return .portrait
}

这适用于 Swift 4 和 Swift 5。您可以在 AppDelegate.swift 中使用以下代码:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    guard let rootViewController = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController),
     (rootViewController.responds(to: Selector(("canRotate")))) else {
        // Only allow portrait (standard behaviour)
        return .portrait;
    }
    // Unlock landscape view orientations for this view controller
    return .allButUpsideDown;
}

private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
    guard rootViewController != nil else { return nil }
    
    guard !(rootViewController.isKind(of: (UITabBarController).self)) else{
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
    }
    guard !(rootViewController.isKind(of:(UINavigationController).self)) else{
        return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
    }
    guard !(rootViewController.presentedViewController != nil) else {
        return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
    }
    return rootViewController
}

然后您可以通过重写 shouldAutorotate

使自定义 UIViewController 旋转

有时当您使用自定义导航流程(这可能会变得非常复杂)时,上述解决方案可能并不总是有效。此外,如果您有多个 ViewController 需要支持多个方向,它可能会变得非常乏味。

这是我找到的一个相当快速的解决方案。定义一个 class OrientationManager 并使用它来更新 AppDelegate 中支持的方向:

class OrientationManager {
    static var landscapeSupported: Bool = false
}

然后在 AppDelegate 中放置您想要的特定情况的方向:

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        if OrientationManager.landscapeSupported {
            return .allButUpsideDown
        }
        return .portrait
    }

然后在 ViewController 中您想要多个导航更新 OrientationManager:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    OrientationManager.landscapeSupported = true
}

此外,当您退出此 ViewController:

时,请不要忘记再次更新它
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    OrientationManager.landscapeSupported = false
    //The code below will automatically rotate your device's orientation when you exit this ViewController
    let orientationValue = UIInterfaceOrientation.portrait.rawValue
    UIDevice.current.setValue(orientationValue, forKey: "orientation")
}

希望对您有所帮助!

更新:

您可能只想添加一个 static func 到您的 Orientation Support Manager class:

    static func setOrientation(_ orientation: UIInterfaceOrientation) {
        let orientationValue = orientation.rawValue
        UIDevice.current.setValue(orientationValue, forKey: "orientation")
        landscapeSupported = orientation.isLandscape
    }

然后您可以在需要将方向设置回纵向时调用此函数。这也将更新静态 landscapeSupported 值:

OSM.setOrientation(.portrait)

只是想分享我的解决方案,因为有人花了太多时间在应用程序中旋转一个视图控制器:

var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { get }

覆盖这个 UIViewController method 帮助我完成了我需要的事情。

  1. 在要旋转的视图控制器上执行此操作以横向向左旋转:

override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { return UIInterfaceOrientation.landscapeLeft }

  1. 确保从项目设置中启用所需方向的旋转:

  1. 并将此添加到 AppDelegate 以禁用其他屏幕的旋转:

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

Swift 5

另一个答案,这个涵盖了 isBeingDismissed 案例。

在 AppDelegate 中:

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        if
            let vvc = navigationController?.visibleViewController,
            vvc is YOURViewControllerClassName &&
            !vvc.isBeingDismissed
        {
            return UIInterfaceOrientationMask.landscape
        } else {
            return UIInterfaceOrientationMask.portrait
        }
    }

Swift 5 使用标记协议

这里几个答案的组合版本,我认为是更 readable/elegant 的实现。 (源自此处的早期答案,并非我的原创作品!)

protocol RotatableViewController {
    // No content necessary, marker protocol
}

class MyViewController: UIViewController, RotatableViewController {
    // normal content... nothing more required
}

extension AppDelegate {

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        guard
            let rootVc = self.topViewControllerWithRootViewController(rootViewController: window?.rootViewController),
            rootVc.isBeingDismissed == false,
            let _ = rootVc as? RotatableViewController
        else {
            return .portrait  // Some condition not met, so default answer for app
        }
        // Conditions met, is rotatable:
        return .allButUpsideDown
    }


    private func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController? {
        if (rootViewController == nil) {
            return nil
        }
        if (rootViewController.isKind(of: UITabBarController.self)) {
            return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UITabBarController).selectedViewController)
        }
        else if (rootViewController.isKind(of: UINavigationController.self)) {
            return topViewControllerWithRootViewController(rootViewController: (rootViewController as! UINavigationController).visibleViewController)
        }
        else if (rootViewController.presentedViewController != nil) {
            return topViewControllerWithRootViewController(rootViewController: rootViewController.presentedViewController)
        }
        return rootViewController
    }
}

None 这些答案对我有用。从根本上说,AppDelegate 的方法不允许指定 viewController。所以要么 topMost ViewController 是可旋转的,在这种情况下整个视图控制器层次结构都会旋转,或者什么都不会旋转。

不过,我确实在

中找到了一个有希望的答案

它引用了https://developer.apple.com/library/archive/qa/qa1890/_index.html

结合大家的想法,我写出了我认为最优雅的方式。

结果:

    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return (UIApplication.getTopViewController() as? Rotatable == nil) ? .portrait : .allButUpsideDown
    }

将这个扩展添加到你的项目中,它不仅对这个有用:

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return getTopViewController(base: selected)
            }
        }

        if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }

        return base
    }

}

创建协议:

protocol Rotatable {}

并实施它:

class ViewController: UIViewController, Rotatable {
}

解决方案Swift5.1

在 App delegate 中实现这个方法

func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
     if let topController = UIApplication.topViewController() {
     
        if topController.isKind(of: YourSpecificViewController.self) {
            return .all
        }
        return .portrait
     }
    return .portrait
}

然后添加此扩展以获得最顶部ViewController

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
    
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
            
        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return topViewController(base: selected)
            
        } else if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}