繁体   English   中英

在 iOS 13 全屏中呈现模态

[英]Presenting modal in iOS 13 fullscreen

在 iOS 13 中,模态视图控制器在呈现时有一个新行为。

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

我怎样才能防止这种行为并回到旧的全屏模式 vc?

模态行为

谢谢

在 iOS 13 中,正如 WWDC 2019 期间的平台国情咨文所述,Apple 引入了新的默认卡片展示。 为了强制全屏,您必须明确指定它:

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

我添加了一个可能对某人有用的信息。 如果您有任何故事板转场,要回到旧样式,您需要将kind属性设置为Present Modally ,将Presentation属性设置为Full Screen

在此处输入图片说明

我在启动屏幕后的初始视图中遇到了这个问题。 因为我没有定义 segue 或逻辑,所以对我的修复是将演示文稿从自动切换到全屏,如下所示:

fix_storyboard_presentation_default_behavior

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

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
在此处输入图片说明

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/

对于 Objective-C 用户

只需使用此代码

 [vc setModalPresentationStyle: UIModalPresentationFullScreen];

或者,如果您想在 iOS 13.0 中添加它,请使用

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

一个班轮:

modalPresentationStyle需要在正在呈现导航modalPresentationStyle上设置。


iOS 13 及以下 iOS 版本 fullScreen,带有overCurrentContextnavigationController

测试代码

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

modalPresentationStyle需要在navigationController 中设置。

我为 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
}

提示:如果您将present 调用到嵌入在NavigationControllerViewController ,您必须将NavigationController设置为.fullScreen而不是VC。

您可以像@davidbates 那样执行此操作,也可以以编程方式(如@pascalbros)执行此操作。

这同样适用于UITabViewController

NavigationController的示例场景:

在此处输入图片说明

    //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. 将演示样式设置为全屏

    全屏

  2. 将顶部栏设置为半透明导航栏

顶栏

这对我有用

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

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

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

vc.modalPresentationStyle = .fullScreen

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

在iOS 13中,出现模态视图控制器时有一个新行为。

现在默认情况下它不是全屏显示,当我尝试向下滑动时,该应用程序会自动关闭View Controller。

如何防止这种行为并返回到旧的全屏模式vc?

情态行为

谢谢

快速解决。 上面已经有很好的答案。 我还添加了我的快速 2 点输入,显示在屏幕截图中。

  1. 如果您不使用Navigation Controller则从Right Menu Inspector中将演示文稿设置为Full Screen

  2. 如果您使用的是Navigation Controller那么默认情况下它会全屏显示,您无需执行任何操作。

在此处输入图片说明

这是一个简单的解决方案,无需编写一行代码。

  • 在 Storyboard 中选择 View Controller
  • 选择属性检查器
  • 按照下图将演示文稿“自动”设置为“全屏”

此更改使 iPad 应用程序的行为符合预期,否则新屏幕将作为弹出窗口显示在屏幕中央。

在此处输入图片说明

这是Objective-C的解决方案

UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
ViewController *vc = [storyBoard instantiateViewControllerWithIdentifier:@"ViewController"];

vc.modalPresentationStyle = UIModalPresentationFullScreen;

[self presentViewController:vc animated:YES completion:nil];

如果您有一个带有嵌入式导航控制器的屏幕的 UITabController,您必须将 UITabController Presentation设置为 FullScreen,如下图所示

在此处输入图片说明

这是我使用类别在 ObjectiveC 中的修复版本。 使用这种方法,您将拥有默认的 UIModalPresentationStyleFullScreen 行为,直到另一个明确设置。

#import "UIViewController+Presentation.h"
#import "objc/runtime.h"

@implementation UIViewController (Presentation)

- (void)setModalPresentationStyle:(UIModalPresentationStyle)modalPresentationStyle {
    [self setPrivateModalPresentationStyle:modalPresentationStyle];
}

-(UIModalPresentationStyle)modalPresentationStyle {
    UIModalPresentationStyle style = [self privateModalPresentationStyle];
    if (style == NSNotFound) {
        return UIModalPresentationFullScreen;
    }
    return style;
}

- (void)setPrivateModalPresentationStyle:(UIModalPresentationStyle)modalPresentationStyle {
    NSNumber *styleNumber = [NSNumber numberWithInteger:modalPresentationStyle];
     objc_setAssociatedObject(self, @selector(privateModalPresentationStyle), styleNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIModalPresentationStyle)privateModalPresentationStyle {
    NSNumber *styleNumber = objc_getAssociatedObject(self, @selector(privateModalPresentationStyle));
    if (styleNumber == nil) {
        return NSNotFound;
    }
    return styleNumber.integerValue;
}

@end

所有其他答案都足够了,但对于像我们这样的大型项目以及在代码和故事板中进行导航的情况,这是一项艰巨的任务。

在此处输入图片说明

对于那些积极使用 Storyboard 的人。 这是我的建议:使用正则表达式。

以下格式不适用于全屏页面:

<segue destination="Bof-iQ-svK" kind="presentation" identifier="importSystem" modalPresentationStyle="fullScreen" id="bfy-FP-mlc"/>

以下格式适用于全屏页面:

<segue destination="7DQ-Kj-yFD" kind="presentation" identifier="defaultLandingToSystemInfo" modalPresentationStyle="fullScreen" id="Mjn-t2-yxe"/>

以下与 VS CODE 兼容的正则表达式会将所有旧样式页面转换为新样式页面。 如果您使用其他正则表达式引擎/文本编辑器,您可能需要转义特殊字符。

搜索正则表达式

<segue destination="(.*)"\s* kind="show" identifier="(.*)" id="(.*)"/>

替换正则表达式

<segue destination="$1" kind="presentation" identifier="$2" modalPresentationStyle="fullScreen" id="$3"/>

最初,modalPresentationStyle 的默认值是fullscreen ,但在iOS 13 中它更改为UIModalPresentationStyle.automatic

如果要制作全屏视图控制器,则必须将modalPresentationStyle更改为fullScreen

有关更多详细信息,请参阅UIModalPresentationStyle 苹果文档,并参阅苹果人机界面指南以了解应在何处使用哪种模式。

let Obj = MtViewController()
Obj.modalPresentationStyle = .overFullScreen
self.present(Obj, animated: true, completion: nil)

// 如果您想禁用滑动以关闭它,请添加行

Obj.isModalInPresentation = true

查看Apple 文档了解更多信息。

你可以很容易地打开你的故事板作为源代码并搜索kind="presentation" ,在所有带有 kind =presentation 的海格标签中添加一个额外的属性modalPresentationStyle="fullScreen"

我通过使用方法 swizzling(Swift 4.2) 实现了它:

创建一个 UIViewController 扩展如下

extension UIViewController {

    @objc private func swizzled_presentstyle(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?) {

        if #available(iOS 13.0, *) {
            if viewControllerToPresent.modalPresentationStyle == .automatic || viewControllerToPresent.modalPresentationStyle == .pageSheet {
                viewControllerToPresent.modalPresentationStyle = .fullScreen
            }
        }

        self.swizzled_presentstyle(viewControllerToPresent, animated: animated, completion: completion)
    }

     static func setPresentationStyle_fullScreen() {

        let instance: UIViewController = UIViewController()
        let aClass: AnyClass! = object_getClass(instance)

        let originalSelector = #selector(UIViewController.present(_:animated:completion:))
        let swizzledSelector = #selector(UIViewController.swizzled_presentstyle(_:animated:completion:))

        let originalMethod = class_getInstanceMethod(aClass, originalSelector)
        let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector)
        if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
        method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}

在 AppDelegate 中,在 application:didFinishLaunchingWithOptions: 中调用 swizzling 代码:

UIViewController.setPresentationStyle_fullScreen()

navigationController.modalPresentationStyle设置为.fullScreen在这里已经重复了一千多次,但让我向您展示另一个阻止程序,它导致UIViewController / UINavigationController没有fullscreen显示,即使所有属性都设置正确。

就我而言,罪魁祸首隐藏在这一行中

navigationController?.presentationController?.delegate = self

显然,在设置UIAdaptivePresentationControllerDelegate您需要在可选委托方法中指定演示样式

    public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
        presentationStyle
    }

为 UIViewController 创建一个类别(比如 UIViewController+PresentationStyle)。 将以下代码添加到其中。

 -(UIModalPresentationStyle)modalPresentationStyle{
     return UIModalPresentationStyleFullScreen;
}

另一种方法是在您的应用程序中拥有您自己的基本视图控制器组件,并仅通过基本设置实现指定和必需的初始化程序,如下所示:

class MyBaseViewController: UIViewController {

//MARK: Initialisers

/// Alternative initializer which allows you to set the modal presentation syle
/// - Parameter modalStyle: the presentation style to be used
init(with modalStyle:UIModalPresentationStyle) {
    super.init(nibName: nil, bundle: nil)
    self.setup(modalStyle: modalStyle)
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    // default modal presentation style as fullscreen
    self.setup(modalStyle: .fullScreen)
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    // default modal presentation style as fullscreen
    self.setup(modalStyle: .fullScreen)
}

//MARK: Private

/// Setup the view
///
/// - Parameter modalStyle: indicates which modal presentation style to be used
/// - Parameter modalPresentation: default true, it prevent modally presented view to be dismissible with the default swipe gesture
private func setup(modalStyle:UIModalPresentationStyle, modalPresentation:Bool = true){
    if #available(iOS 13, *) {
        self.modalPresentationStyle = modalStyle
        self.isModalInPresentation = modalPresentation
    }
}

注意:如果您的视图控制器包含在实际以模态呈现的导航控制器中,那么导航控制器应该以相同的方式解决问题(意思是,以相同的方式自定义您的自定义导航控制器组件

在 iOS 13.1 和 iOS 12.4 上的 Xcode 11.1 上测试

希望能帮助到你

在iOS 13中,出现模态视图控制器时有一个新行为。

现在默认情况下它不是全屏显示,当我尝试向下滑动时,该应用程序会自动关闭View Controller。

如何防止这种行为并返回到旧的全屏模式vc?

情态行为

谢谢

在iOS 13中,出现模态视图控制器时有一个新行为。

现在默认情况下它不是全屏显示,当我尝试向下滑动时,该应用程序会自动关闭View Controller。

如何防止这种行为并返回到旧的全屏模式vc?

情态行为

谢谢

由于无法解决问题,我不得不将某些导航控制器切换回全屏模式。

如果我有两个设置为自动,一个被称为另一个,则第二个控制器将在屏幕边缘出现右键。

第一个控制器如下所示:

在此处输入图片说明

然后,如果我使用ViewController选择另一个NavigationController ,它看起来像这样:

在此处输入图片说明

为什么在第二个ViewController ,右键单击边缘而不是边缘? 还要注意减小的高度。

如果我将第一个导航控制器更改为全屏,则第二个导航控制器将正确显示。

覆盖 modalPresentationStyle将修复使用或不使用编码器创建的UIViewControllers的样式。

优点:

  • 设置它的单一位置。
  • 无需知道应该设置哪个 init 或 awake 方法

坏处:

  • 您不能像界面生成器或代码配置那样从外部更改它

解决方案:

override var modalPresentationStyle: UIModalPresentationStyle {
    get { .fullScreen }
    set { }
}

上面的回答和建议是对的,下面是另一个版本,以编程方式使用的有效方法。

#1 创建了一个 UIView 扩展

#2 创建了一个方法 ()

//#1
extension UIViewController {

//#2
func presentLocal(_ viewControllerToPresent: UIViewController, animated flag: 
Bool, completion: (() -> Void)? = nil) {

//Reusing below 2 lines :-)
viewControllerToPresent.modalPresentationStyle = .overCurrentContext
self.present(viewControllerToPresent, animated: flag, completion: completion)

  }
}

调用如下

let vc = MyViewController()
let nc = UINavigationController(rootViewController: vc)
sourceView.presentLocal(nc, animated: true, completion: nil)

或者

let vc = MyViewController()
sourceView.presentLocal(vc, animated: true, completion: nil)

在iOS 13中,出现模态视图控制器时有一个新行为。

现在默认情况下它不是全屏显示,当我尝试向下滑动时,该应用程序会自动关闭View Controller。

如何防止这种行为并返回到旧的全屏模式vc?

情态行为

谢谢

在iOS 13中,出现模态视图控制器时有一个新行为。

现在默认情况下它不是全屏显示,当我尝试向下滑动时,该应用程序会自动关闭View Controller。

如何防止这种行为并返回到旧的全屏模式vc?

情态行为

谢谢

class MyViewController: UIViewController {

    convenience init() {
        self.init(nibName:nil, bundle:nil)
        self.modalPresentationStyle = .fullScreen
    }

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

而不是为每个视图控制器调用self.modalPresentationStyle = .fullScreen ,您可以self.modalPresentationStyle = .fullScreen UIViewController 并在任何地方使用MyViewController

在Objective-C中,我找到了两种方法。

我注意到两个枚举modalPresentationStyles都小于零

typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {
    UIModalPresentationFullScreen = 0,
    UIModalPresentationPageSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
    UIModalPresentationFormSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
    UIModalPresentationCurrentContext API_AVAILABLE(ios(3.2)),
    UIModalPresentationCustom API_AVAILABLE(ios(7.0)),
    UIModalPresentationOverFullScreen API_AVAILABLE(ios(8.0)),
    UIModalPresentationOverCurrentContext API_AVAILABLE(ios(8.0)),
    UIModalPresentationPopover API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(tvos),
    UIModalPresentationBlurOverFullScreen API_AVAILABLE(tvos(11.0)) API_UNAVAILABLE(ios) API_UNAVAILABLE(watchos),
    UIModalPresentationNone API_AVAILABLE(ios(7.0)) = -1,
    UIModalPresentationAutomatic API_AVAILABLE(ios(13.0)) = -2,
};
  • 覆盖您的基本ViewController方法(我建议)
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
    if ([self isKindOfClass:[UINavigationController class]]) {
//default is UIModalPresentationPageSheet
        viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;
    }
    else if ([self isKindOfClass:[UITabBarController class]]) {
        viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;
    }
    else if (viewControllerToPresent.modalPresentationStyle < 0){
        viewControllerToPresent.modalPresentationStyle = UIModalPresentationFullScreen;
    }
    [super presentViewController:viewControllerToPresent animated:flag completion:completion];
}

迅速

 override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        if self as? UINavigationController != nil {
            //UINavigationController default is UIModalPresentationPageSheet
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        else if self as? UITabBarController != nil {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        else if viewControllerToPresent.modalPresentationStyle.rawValue < 0 {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
  • 方法交流(我不推荐)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM