【发布时间】:2015-03-09 09:29:25
【问题描述】:
因为我的应用支持所有方向。我想只将纵向模式锁定到特定的 UIViewController。
例如假设它是选项卡式应用程序,当登录视图以模态方式出现时,我只希望登录视图为纵向模式,无论用户如何旋转设备或当前设备方向如何
【问题讨论】:
标签: swift uiviewcontroller portrait device-orientation
因为我的应用支持所有方向。我想只将纵向模式锁定到特定的 UIViewController。
例如假设它是选项卡式应用程序,当登录视图以模态方式出现时,我只希望登录视图为纵向模式,无论用户如何旋转设备或当前设备方向如何
【问题讨论】:
标签: swift uiviewcontroller portrait device-orientation
当您拥有复杂的视图层次结构时,事情可能会变得非常混乱,例如拥有多个导航控制器和/或选项卡视图控制器。
这个实现将它放在各个视图控制器上,以便在它们想要锁定方向时进行设置,而不是依靠 App Delegate 通过遍历子视图来找到它们。
斯威夫特 3、4、5
在 AppDelegate 中:
/// set orientations you want to be allowed in this property by default
var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientationLock
}
在其他一些全局结构或辅助类中,我在这里创建了 AppUtility:
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
/// OPTIONAL Added method to adjust lock and rotate to the desired orientation
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
UINavigationController.attemptRotationToDeviceOrientation()
}
}
然后在您想要锁定方向的所需 ViewController 中:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
AppUtility.lockOrientation(.portrait)
// Or to rotate and lock
// AppUtility.lockOrientation(.portrait, andRotateTo: .portrait)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Don't forget to reset when view is being removed
AppUtility.lockOrientation(.all)
}
如果是 iPad 或通用应用程序
确保在目标设置 -> 常规 -> 部署信息中选中“需要全屏”。 supportedInterfaceOrientationsFordelegate 如果没有被选中,将不会被调用。
【讨论】:
Requires full screen,这将阻止应用程序可用于 Slide Over 和 Split View。请参阅 Apple 的Adopting Multitasking Enhancements on iPad。我有一个answer 不需要启用Requires full screen
斯威夫特 4
var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return self.orientationLock
}
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
你的 ViewController 如果您只需要纵向,请添加以下行。您必须将此应用于所有需要显示纵向模式的 ViewController。
override func viewWillAppear(_ animated: Bool) {
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}
和 这将根据设备物理方向为其他 Viewcontroller 制作屏幕方向。
override func viewWillDisappear(_ animated: Bool) {
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.all)
}
【讨论】:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return self.orientationLock ? 替换了应用程序委托中的原始func application
Swift 3 和 4
像这样设置特定 UIViewControllers 的 supportedInterfaceOrientations 属性:
class MyViewController: UIViewController {
var orientations = UIInterfaceOrientationMask.portrait //or what orientation you want
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
get { return self.orientations }
set { self.orientations = newValue }
}
override func viewDidLoad() {
super.viewDidLoad()
}
//...
}
更新
此解决方案仅在您的viewController未嵌入UINavigationController 时有效,因为方向继承自父 viewController。
对于这种情况,您可以创建UINavigationViewController 的子类并在其上设置这些属性。
【讨论】:
对于新版本的 Swift 试试这个
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.portrait
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return UIInterfaceOrientation.portrait
}
【讨论】:
添加此代码以强制纵向并锁定它:
override func viewDidLoad() {
super.viewDidLoad()
// Force the device in portrait mode when the view controller gets loaded
UIDevice.currentDevice().setValue(UIInterfaceOrientation.Portrait.rawValue, forKey: "orientation")
}
override func shouldAutorotate() -> Bool {
// Lock autorotate
return false
}
override func supportedInterfaceOrientations() -> Int {
// Only allow Portrait
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
// Only allow Portrait
return UIInterfaceOrientation.Portrait
}
在您的 AppDelegate 中 - 将supportedInterfaceOrientationsForWindow 设置为您希望整个应用程序支持的任何方向:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.All
}
【讨论】:
这是针对您的问题和其他相关问题的通用解决方案。
1.创建辅助类 UIHelper 并放入以下方法:
/**This method returns top view controller in application */
class func topViewController() -> UIViewController?
{
let helper = UIHelper()
return helper.topViewControllerWithRootViewController(rootViewController: UIApplication.shared.keyWindow?.rootViewController)
}
/**This is a recursive method to select the top View Controller in a app, either with TabBarController or not */
private func topViewControllerWithRootViewController(rootViewController:UIViewController?) -> UIViewController?
{
if(rootViewController != nil)
{
// UITabBarController
if let tabBarController = rootViewController as? UITabBarController,
let selectedViewController = tabBarController.selectedViewController {
return self.topViewControllerWithRootViewController(rootViewController: selectedViewController)
}
// UINavigationController
if let navigationController = rootViewController as? UINavigationController ,let visibleViewController = navigationController.visibleViewController {
return self.topViewControllerWithRootViewController(rootViewController: visibleViewController)
}
if ((rootViewController!.presentedViewController) != nil) {
let presentedViewController = rootViewController!.presentedViewController;
return self.topViewControllerWithRootViewController(rootViewController: presentedViewController!);
}else
{
return rootViewController
}
}
return nil
}
2。根据您的期望行为创建一个协议,因为您的具体情况将是纵向的。
协议orientationIsOnlyPortrait {}
注意:如果需要,请将其添加到 UIHelper 类的顶部。
3.扩展您的视图控制器
在你的情况下:
class Any_ViewController: UIViewController,orientationIsOnlyPortrait {
....
}
4.在应用委托类中添加此方法:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
let presentedViewController = UIHelper.topViewController()
if presentedViewController is orientationIsOnlyPortrait {
return .portrait
}
return .all
}
最后说明:
【讨论】:
这个帖子中有很多很棒的答案,但没有一个完全符合我的需求。我有一个选项卡式应用程序,每个选项卡中都有导航控制器,一个视图需要旋转,而其他视图需要纵向锁定。由于某种原因,导航控制器没有正确调整其子视图的大小。通过结合this答案找到了解决方案(在Swift 3中),布局问题消失了。按照@bmjohns 的建议创建结构:
import UIKit
struct OrientationLock {
static func lock(to orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lock(to orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation: UIInterfaceOrientation) {
self.lock(to: orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
然后继承UITabBarController:
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.delegate = self
}
func tabBarControllerSupportedInterfaceOrientations(_ tabBarController: UITabBarController) -> UIInterfaceOrientationMask {
if tabBarController.selectedViewController is MyViewControllerNotInANavigationControllerThatShouldRotate {
return .allButUpsideDown
} else if let navController = tabBarController.selectedViewController as? UINavigationController, navController.topViewController is MyViewControllerInANavControllerThatShouldRotate {
return .allButUpsideDown
} else {
//Lock view that should not be able to rotate
return .portrait
}
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is MyViewControllerNotInANavigationControllerThatShouldRotate {
OrientationLock.lock(to: .allButUpsideDown)
} else if let navController = viewController as? UINavigationController, navController.topViewController is MyViewControllerInANavigationControllerThatShouldRotate {
OrientationLock.lock(to: .allButUpsideDown)
} else {
//Lock orientation and rotate to desired orientation
OrientationLock.lock(to: .portrait, andRotateTo: .portrait)
}
return true
}
}
别忘了把storyboard中TabBarController的类改成新创建的子类。
【讨论】:
这是一种适用于 Swift 4.2 (iOS 12.2) 的简单方法,将其放入您要禁用 shouldAutorotate 的 UIViewController:
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
.portrait 部分告诉它保持哪个方向,您可以随意更改。选项有:.portrait、.all、.allButUpsideDown、.landscape、.landscapeLeft、.landscapeRight、.portraitUpsideDown。
【讨论】:
要将横向方向设置为您应用的所有视图,并且只允许一个视图用于所有方向(例如,能够添加相机胶卷):
在 AppDelegate.swift 中:
var adaptOrientation = false
在:didFinishLaunchingWithOptions
NSNotificationCenter.defaultCenter().addObserver(self, selector: "adaptOrientationAction:", name:"adaptOrientationAction", object: nil)
AppDelegate.swift 中的其他地方:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
return checkOrientation(self.window?.rootViewController)
}
func checkOrientation(viewController:UIViewController?)-> Int{
if (adaptOrientation == false){
return Int(UIInterfaceOrientationMask.Landscape.rawValue)
}else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
func adaptOrientationAction(notification: NSNotification){
if adaptOrientation == false {
adaptOrientation = true
}else {
adaptOrientation = false
}
}
然后在视图中选择你想要的所有方向:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "YOURSEGUE") {
NSNotificationCenter.defaultCenter().postNotificationName("adaptOrientationAction", object: nil)
}
}
override func viewWillAppear(animated: Bool) {
if adaptOrientation == true {
NSNotificationCenter.defaultCenter().postNotificationName("adaptOrientationAction", object: nil)
}
}
最后一件事是勾选设备方向: - 肖像 - 左侧景观 - 横向右侧
【讨论】:
用
创建新的扩展import UIKit
extension UINavigationController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
}
extension UITabBarController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
}
【讨论】:
在纵向和横向上锁定和更改方向的最佳解决方案:
在 YouTube 上观看此视频:
https://m.youtube.com/watch?v=4vRrHdBowyo
本教程最好也最简单。
或使用以下代码:
// 1- 在第二个 viewcontroller 中我们设置 Landscapeleft,在第一个 viewcontroller 中我们设置 portrat:
// 2-如果你使用NavigationController,你应该添加扩展
import UIKit
class SecondViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIDevice.current.setValue(UIInterfaceOrientation.landscapeLeft.rawValue, forKey: "orientation")
}
override open var shouldAutorotate: Bool {
return false
}
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeLeft
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeLeft
}
override func viewDidLoad() {
super.viewDidLoad()
}
//write The rest of your code in here
}
//if you use NavigationController, you should add this extension
extension UINavigationController {
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return topViewController?.supportedInterfaceOrientations ?? .allButUpsideDown
}
}
【讨论】:
bmjohns -> 你是我的救星。这是唯一可行的解决方案(使用 AppUtility 结构)
我已经创建了这个类:
class Helper{
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
/// OPTIONAL Added method to adjust lock and rotate to the desired orientation
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
}
并按照您的指示进行操作,一切都适用于 Swift 3 -> xcode 版本 8.2.1
【讨论】:
从 iOS 10 和 11 开始,iPad 支持 Slide Over 和 Split View。要在 Slide Over 和 Split View 中启用应用程序,Requires full screen必须取消选中。这意味着无法使用已接受的答案如果应用程序想要支持 Slide Over 和 Split View。查看 Apple 的在 iPad 上采用多任务增强功能here的更多信息。
我有一个解决方案,允许 (1) 取消选中 Requires full screen,(2) 在 appDelegate 中只实现一个功能(特别是如果您不想/不能修改目标视图控制器), (3) 避免递归调用。不需要辅助类或扩展。
appDelegate.swift (Swift 4)
func application(_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// Search for the visible view controller
var vc = window?.rootViewController
// Dig through tab bar and navigation, regardless their order
while (vc is UITabBarController) || (vc is UINavigationController) {
if let c = vc as? UINavigationController {
vc = c.topViewController
} else if let c = vc as? UITabBarController {
vc = c.selectedViewController
}
}
// Look for model view controller
while (vc?.presentedViewController) != nil {
vc = vc!.presentedViewController
}
print("vc = " + (vc != nil ? String(describing: type(of: vc!)) : "nil"))
// Final check if it's our target class. Also make sure it isn't exiting.
// Otherwise, system will mistakenly rotate the presentingViewController.
if (vc is TargetViewController) && !(vc!.isBeingDismissed) {
return [.portrait]
}
return [.all]
}
编辑
@bmjohns 指出 iPad 上不调用此函数。我验证了,是的,它没有被调用。所以,我做了更多的测试,发现了一些事实:
Requires full screen,因为我想在 iPad 上启用 Slide Over 和 Slide View。这要求应用程序支持 iPad 的所有 4 个方向,在 Info.plist 中:Supported interface orientations (iPad)。我的应用程序的工作方式与 Facebook 相同:在 iPhone 上,大部分时间它被锁定为纵向。全屏查看图像时,允许用户旋转横向以获得更好的视图。在 iPad 上,用户可以在任何视图控制器中旋转到任何方向。因此,当 iPad 放在 Smart Cover 上(横向左侧)时,该应用看起来不错。
iPad 调用application(_:supportedInterfaceOrientationsFor),Info.plist 中只保留iPad 的头像。该应用程序将失去 Slide Over + Split View 功能。但是您可以在一个地方锁定或解锁任何视图控制器的方向,而无需修改 ViewController 类。
最后,这个函数在视图控制器的生命周期中被调用,当视图被显示/移除时。如果您的应用需要在其他时间锁定/解锁/更改方向,它可能无法正常工作
【讨论】:
实际测试的解决方案。在我的示例中,我需要我的整个应用程序处于纵向模式,但只有一个屏幕的方向应该处于横向模式。
AppDelegate 中的代码如上述答案所述。
var orientationLock = UIInterfaceOrientationMask.all
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
{
return self.orientationLock
}
struct AppUtility {
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
if let delegate = UIApplication.shared.delegate as? AppDelegate {
delegate.orientationLock = orientation
}
}
static func lockOrientation(_ orientation: UIInterfaceOrientationMask, andRotateTo rotateOrientation:UIInterfaceOrientation) {
self.lockOrientation(orientation)
UIDevice.current.setValue(rotateOrientation.rawValue, forKey: "orientation")
}
}
然后在您的横向视图控制器将被呈现/推送之前写下此代码。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.portrait, andRotateTo: UIInterfaceOrientation.portrait)
}
然后在实际的viewcontroller中写下这段代码(横向视图)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
AppDelegate.AppUtility.lockOrientation(UIInterfaceOrientationMask.landscape, andRotateTo: UIInterfaceOrientation.landscape)
}
【讨论】:
我进行了一些实验,并设法找到了解决这个问题的干净解决方案。 该方法基于通过 view->tag
进行的视图标记在目标 ViewController 中,只需将标签分配给根视图,如下面的代码示例所示:
class MyViewController: BaseViewController {
// declare unique view tag identifier
static let ViewTag = 2105981;
override func viewDidLoad()
{
super.viewDidLoad();
// assign the value to the current root view
self.view.tag = MyViewController.ViewTag;
}
最后在 AppDelegate.swift 中检查当前显示的视图是否是我们标记的视图:
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask
{
if (window?.viewWithTag(DesignerController.ViewTag)) != nil {
return .portrait;
}
return .all;
}
这个方法已经用我的模拟器测试过了,看起来效果不错。
注意:如果当前 MVC 与导航堆栈中的某些子 ViewController 重叠,也会找到标记的视图。
【讨论】:
感谢@bmjohn 的上述回答。这是该答案代码的工作 Xamarin / C# 版本,以节省其他人的转录时间:
AppDelegate.cs
public UIInterfaceOrientationMask OrientationLock = UIInterfaceOrientationMask.All;
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations(UIApplication application, UIWindow forWindow)
{
return this.OrientationLock;
}
Static OrientationUtility.cs 类:
public static class OrientationUtility
{
public static void LockOrientation(UIInterfaceOrientationMask orientation)
{
var appdelegate = (AppDelegate) UIApplication.SharedApplication.Delegate;
if(appdelegate != null)
{
appdelegate.OrientationLock = orientation;
}
}
public static void LockOrientation(UIInterfaceOrientationMask orientation, UIInterfaceOrientation RotateToOrientation)
{
LockOrientation(orientation);
UIDevice.CurrentDevice.SetValueForKey(new NSNumber((int)RotateToOrientation), new NSString("orientation"));
}
}
视图控制器:
public override void ViewDidAppear(bool animated)
{
base.ViewWillAppear(animated);
OrientationUtility.LockOrientation(UIInterfaceOrientationMask.Portrait, UIInterfaceOrientation.Portrait);
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
OrientationUtility.LockOrientation(UIInterfaceOrientationMask.All);
}
【讨论】: