在iOS 13中全屏显示模态窗口

644

iOS 13中,模态视图控制器的呈现方式有了新的行为。

现在默认情况下不是全屏显示的,当我尝试向下滑动时,应用程序会自动关闭该视图控制器。

如何防止这种行为并回到旧的全屏模态VC?

模态行为

谢谢

28个回答

832

iOS 13中,根据2019年WWDC的平台总览,苹果公司引入了一个新的默认卡片式呈现方式。为了强制全屏显示,您需要明确指定:

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen //or .overFullScreen for transparency
self.present(vc, animated: true, completion: nil)

19
我不会抱有期望。过去,苹果公司经常只在你与当前版本的 SDK 进行链接时才更改默认设置。希望我们在链接旧版本时能够获得旧的行为。 - DrMickeyLauer
11
我可以证实,使用Xcode-10构建的应用程序在iOS 13模拟器上运行仍然默认全屏。正如@DrMickeyLauer所说,使用Xcode 11构建将应用程序选择新行为。使用isModalInPresentation阻止滑动手势解除。有关更多详细信息,请参见我的博客文章:https://medium.com/@hacknicity/view-controller-presentation-changes-in-ios-13-ac8c901ebc4e - Geoff Hackworth
8
建议使用".fullScreen"而不是".overFullScreen"。".fullScreen"会触发视图控制器的"viewWillAppear"和"viewDidAppear"方法,而".overFullScreen"则不会。 - PiterPan
6
时间已经过去了,.automatic风格已经成为默认的风格,并且对于大多数视图控制器来说,它是.pageSheet风格。然而,一些系统视图控制器可能会将其映射到不同的风格。 - Jakub Truhlář
1
请记住,如果您使用导航控制器,则应将 modalPresentationStyle 添加到导航控制器而不是呈现的视图控制器。 - swiftcode
显示剩余4条评论

251

我提供一些可能对某些人有用的信息。如果您有任何故事板segue,要返回到旧样式,您需要将 kind 属性设置为 Present Modally,并将 Presentation 属性设置为 Full Screen

输入图片描述


3
在接口构建器中是否有设置 isModalInPresentation 的方法? - Bradley Thomas
你刚刚解决了我的问题,谢谢!我已经折腾了3天了... - todbott

130
我在启动屏幕后的初始视图上遇到了这个问题。由于我没有定义转场或逻辑,所以对我来说解决方法是将演示从“自动”切换到“全屏”,如下所示:

fix_storyboard_presentation_default_behavior


2
有没有办法通过编程方式来处理所有视图,而不是一个一个地通过Storyboard来处理? - The1993
1
@ShobhitPuri 请看这里Omreyh的第一个解决方案 https://dev59.com/ElMI5IYBdhLWcg3wV6Jr#58255416 - The1993
1
哇,这就是我问题的答案。感谢您的提示!对于其他人也在研究此问题的人来说,这是解决从后台重新打开应用程序后出现奇怪行为的方法。在我的应用程序中,从后台打开会将我的启动屏幕(初始视图控制器)作为卡片呈现样式叠加,并且从此处开始的任何segue都会交叉淡入淡出,而不是使用我定义的segue样式。如果我关闭应用程序(双击主页按钮并向上滑动,然后重新打开),那么一切都没问题,但是任何其他启动都会导致这种奇怪的行为。再次感谢! - Justin

122

有多种方法可以做到这一点,我认为每种方法可能适合一个项目但不适合另一个项目,所以我想把它们保存在这里,也许其他人会遇到不同的情况。

1- 覆盖当前

如果您有一个BaseViewController,您可以覆盖present(_ viewControllerToPresent: animated flag: completion:)方法。

class BaseViewController: UIViewController {

  // ....

  override func present(_ viewControllerToPresent: UIViewController,
                        animated flag: Bool,
                        completion: (() -> Void)? = nil) {
    viewControllerToPresent.modalPresentationStyle = .fullScreen
    super.present(viewControllerToPresent, animated: flag, completion: completion)
  }

  // ....
}

使用这种方式,您无需对任何present调用进行任何更改,因为我们只是覆盖了present方法。

2-一个扩展:

extension UIViewController {
  func presentInFullScreen(_ viewController: UIViewController,
                           animated: Bool,
                           completion: (() -> Void)? = nil) {
    viewController.modalPresentationStyle = .fullScreen
    present(viewController, animated: animated, completion: completion)
  }
}

用法:

presentInFullScreen(viewController, animated: true)

3- 对于一个UIViewController

let viewController = UIViewController()
viewController.modalPresentationStyle = .fullScreen
present(viewController, animated: true, completion: nil)

4- 从故事板中

选择一个segue并将其演示设置为FullScreen
enter image description here

5- 方法交换

extension UIViewController {

  static func swizzlePresent() {

    let orginalSelector = #selector(present(_: animated: completion:))
    let swizzledSelector = #selector(swizzledPresent)

    guard let orginalMethod = class_getInstanceMethod(self, orginalSelector), let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else{return}

    let didAddMethod = class_addMethod(self,
                                       orginalSelector,
                                       method_getImplementation(swizzledMethod),
                                       method_getTypeEncoding(swizzledMethod))

    if didAddMethod {
      class_replaceMethod(self,
                          swizzledSelector,
                          method_getImplementation(orginalMethod),
                          method_getTypeEncoding(orginalMethod))
    } else {
      method_exchangeImplementations(orginalMethod, swizzledMethod)
    }

  }

  @objc
  private func swizzledPresent(_ viewControllerToPresent: UIViewController,
                               animated flag: Bool,
                               completion: (() -> Void)? = nil) {
    if #available(iOS 13.0, *) {
      if viewControllerToPresent.modalPresentationStyle == .automatic {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
      }
    }
    swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
   }
}

用法:
在你的AppDelegate中的application(_ application: didFinishLaunchingWithOptions)方法里添加这行代码:

UIViewController.swizzlePresent()

使用这种方式,您不需要对任何现有的调用进行任何更改,因为我们在运行时替换了当前方法的实现。
如果您想了解什么是swizzling,可以查看此链接: https://nshipster.com/swift-objc-runtime/


我在我的项目中有许多 viewControllers,但是没有基类,我不想使用 swizzling,你有没有任何解决方案可以在最少的代码更改下实现? - guru
我使用了 Swizzling,但我添加了 .pageSheet 条件判断... 如果 viewControllerToPresent.modalPresentationStyle == .pageSheet || viewControllerToPresent.modalPresentationStyle == .automatic ,那么 viewControllerToPresent.modalPresentationStyle = .fullScreen - Crash
当我添加应用解决方案1时,状态栏会隐藏。 - Ganesh Pawar
4
Swizzling是一个一度非常有效的解决方案。然而,当使用一些外部sdk如FacebookLogin(截至今天的版本为5.8)和GoogleSignin时,我注意到这种方法会破坏这些流程:在iPad上会出现白屏。这可能是因为它们使用了自己的swizzling方法。 - Tao-Nhan Nguyen

48

针对Objective-C用户

只需使用此代码即可

 [vc setModalPresentationStyle: UIModalPresentationFullScreen];

如果你想在iOS 13.0中特别添加它,那么使用以下方法:

 if (@available(iOS 13.0, *)) {
     [vc setModalPresentationStyle: UIModalPresentationFullScreen];
 } else {
     // Fallback on earlier versions
 }

5
UIModalPresentationFullScreen 可在 iOS 3.2+ 中使用,因此无需添加 if else 条件。 - flame3
2
由于某种原因,在iOS 13.1.2中,只有Obj-c类中这不起作用,modalPresentationStyle仅显示一个pageSheet。这是否也发生在其他人身上? - Sevy11
@Sevy11 我还没有更新到iOS 13.1.2,但在13.1上运行良好。 - 9to5ios

48
提示:如果您调用嵌套在 NavigationController 中的 ViewController ,则必须将 NavigationController 设置为 .fullScreen 而不是VC。您可以像@davidbates一样这样做,或者以编程方式执行此操作(如@pascalbros)。对于UITabViewController也适用相同的规则。关于NavigationController的一个示例场景:enter image description here
    //BaseNavigationController: UINavigationController {}
    let baseNavigationController = storyboard!.instantiateViewController(withIdentifier: "BaseNavigationController")
    var navigationController = UINavigationController(rootViewController: baseNavigationController)
    navigationController.modalPresentationStyle = .fullScreen
    navigationController.topViewController as? LoginViewController
    self.present(navigationViewController, animated: true, completion: nil)

1
这是一个非常有用的观点。很容易忘记并让你发疯(我曾经)。谢谢。 - rmvz3

46

一句话概述:

modalPresentationStyle需要在被呈现的导航控制器上设置。


iOS 13及以下版本中,使用overCurrentContextnavigationController可以实现全屏。

测试代码

let controller = UIViewController()
let navigationController = UINavigationController(rootViewController: controller)
navigationController.modalPresentationStyle = .overCurrentContext
self.navigationController?.present(navigationController, animated: true, completion: nil)

modalPresentationStyle需要在navigationController上设置。


澄清一下:从导航控制器中呈现视图控制器不需要在导航控制器上设置modalPresentationStyle。相反,它应该在被呈现的视图控制器上设置。 但是,如果您要呈现一个导航控制器,则应该在导航控制器上设置'modalPresentationStyle'属性,而不是嵌入的视图控制器。 这种方法适用于iOS 13.3,Xcode 11.3。请参见Yogesh Bharate的答案。 - Womble

42

这对我起了作用

let vc = self.storyboard?.instantiateViewController(withIdentifier: "storyboardID_cameraview1") as! CameraViewController
  
vc.modalPresentationStyle = .fullScreen
    
self.present(vc, animated: true, completion: nil)`

41

我在iOS 13中使用了swizzling技术

import Foundation
import UIKit

private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
       let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIViewController {

    static let preventPageSheetPresentation: Void = {
        if #available(iOS 13, *) {
            _swizzling(forClass: UIViewController.self,
                       originalSelector: #selector(present(_: animated: completion:)),
                       swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
        }
    }()

    @available(iOS 13.0, *)
    @objc private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
                                        animated flag: Bool,
                                        completion: (() -> Void)? = nil) {
        if viewControllerToPresent.modalPresentationStyle == .pageSheet
                   || viewControllerToPresent.modalPresentationStyle == .automatic {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        _swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}

然后放置这个

UIViewController.preventPageSheetPresentation

某个地方

例如在AppDelegate中

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {

    UIViewController.preventPageSheetPresentation
    // ...
    return true
}

我们在某些设备上使用时出现以下错误:致命异常:NSInternalInconsistencyException。在从主线程访问后,不应该在后台线程中对布局引擎进行修改。 - krishnan muthiah pillai
很奇怪,如果没有交换,检查一下是否会发生这种情况(注释掉这行代码 UIViewController.preventPageSheetPresentation),如果仍然发生,请找到实际问题,你在后台线程的某个地方调用了一些布局(我猜测是在网络请求完成时)。 - Maxime Ashurov

29

适用于 iOS 13 和 Swift 5.x 的最新版本

let vc = ViewController(nibName: "ViewController", bundle: nil)

vc.modalPresentationStyle = .fullScreen

self.present(vc, animated: true, completion: nil)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接