UIModalPresentationStyleCustom不调用viewWillDisappear问题

4,557 阅读5分钟

问题

  • A present B,A,B的viewWillAppearviewDidAppearviewWillDisappearviewDidDisappear调用顺序是怎样的?
  • A页面的viewWillDisappearviewDidDisappear在什么情况会调用,什么情况不会调用,为什么?
  • 在自定义了转场之后(modalPresentationStyle设置为custom),怎么让A页面的viewWillDisappearviewDidDisappear调用?

项目中之前modal实现的一个转场场景,忽然需要加一个转场动画,心想就是半个小时搞定的事情,无非就是实现两个delegateUIViewControllerTransitioningDelegateUIViewControllerAnimatedTransitioning,然后自定义一个动画,结果一做,差点陷入坑里

问题1

大概场景是这样的: presentingViewController(A页面)上面有个播放器,然后presentpresentedViewController(B页面)全屏播放

之前由于modalPresentationStyle设置的是UIModalPresentationStyle.fullScreen

那么在转场过程中,两个VC的生命周期大概是这样的

image.png

历史逻辑在presentingViewController(A页面)Disappear时需要做一些操作

问题2

image.png

加入自定义转场,modalPresentationStyle必须设置为UIModalPresentationStyle.custom

modalPresentationStyle设置为UIModalPresentationStyle.custom,那么presentingViewControllerviewWillDisappearviewDidDisappear就不会调用了

至于这里为什么不调用,其实我是知道原因

先说在UIModalPresentationStyle.fullScreen下为什么会调用viewWillDisappearviewDidDisappear

官网解释

When presenting a view controller using the UIModalPresentationFullScreen style, UIKit normally removes the views of the underlying view controller after the transition animations finish. You can prevent the removal of those views by specifying the UIModalPresentationOverFullScreen style instead. You might use that style when the presented view controller has transparent areas that let underlying content show through.

大意:

当使用UIModalPresentationFullScreen样式presenting一个view controller时,UIKit 通常会在过渡动画完成后移除底层视图控制器的视图,你可以通过指定 UIModalPresentationOverFullScreen 样式来防止删除这些视图,当呈现的view controller想让底层内容显示在透明区域时,您可以使用该样式,就是present一个B页面后,下面的presentingViewController(A页面)还在

那么在UIModalPresentationStyle.fullScreen下为什么会调用viewWillDisappearviewDidDisappear就很好解释了,因为在转场时,他会移除下面的viewController

modalPresentationStyleUIModalPresentationOverFullScreen的视图结构

modalPresentationStyle为UIModalPresentationOverFullScreen使的视图结构

modalPresentationStyleUIModalPresentationOverCustom的视图结构

modalPresentationStyle为UIModalPresentationOverFullScreen使的视图结构

可以看到在UIModalPresentationOverCustom模式下,presentingViewController(A页面)还在,没有被移除,这也就解释了,为什么viewWillDisappearviewDidDisappear不调用

再看viewWillDisappearviewDidDisappear的文档,在什么时候,这两个方法会调用

viewwilldisappear

This method is called in response to a view being removed from a view hierarchy. This method is called before the view is actually removed and before any animations are configured

大意:

调用此方法是为了响应从视图层次结构中删除的视图。在实际删除视图之前和配置任何动画之前调用此方法

viewDidDisappear

Notifies the view controller that its view was removed from a view hierarchy

大意

通知视图控制器它的视图已从视图层次结构中删除

那么到了这里,我们可以知道,在modal切换的场景中,presentingViewController(A页面)的viewwilldisappearviewDidDisappear调用,是和modalPresentationStyle有关的

那么modalPresentationStyle作为一个枚举属性,他的每个变量,具体作用又是什么?

modalPresentationStyle定义如下

public enum UIModalPresentationStyle : Int {

    case fullScreen = 0

    @available(iOS 3.2, *)

    case pageSheet = 1

    @available(iOS 3.2, *)

    case formSheet = 2

    @available(iOS 3.2, *)

    case currentContext = 3

    @available(iOS 7.0, *)

    case custom = 4

    @available(iOS 8.0, *)

    case overFullScreen = 5

    @available(iOS 8.0, *)

    case overCurrentContext = 6

    @available(iOS 8.0, *)

    case popover = 7

    @available(iOS 7.0, *)

    case none = -1

    @available(iOS 13.0, *)

    case automatic = -2
}

以下说明摘至详解iOS的presentViewController

  • UIModalPresentation.fullScreen

UIKit默认的presentation style。 使用这种模式时,presented VC的宽高与屏幕相同,并且UIKit会直接使用rootViewController做为presentation context,在此次presentation完成之后,UIKit会将presentation context及其子VC都移出UI栈,这时候观察VC的层级关系,会发现UIWindow下只有presented VC.

  • UIModalPresentation.pageSheet 在常规型设备(大屏手机,例如plus系列以及iPad系列)的水平方向,presented VC的高为当前屏幕的高度,宽为该设备竖直方向屏幕的宽度,其余部分用透明背景做填充。对于紧凑型设备(小屏手机)的水平方向及所有设备的竖直方向,其显示效果与UIModalPresentationFullScreen相同。

  • UIModalPresentation.formSheet 在常规型设备的水平方向,presented VC的宽高均小于屏幕尺寸,其余部分用透明背景填充。对于紧凑型设备的水平方向及所有设备的竖直方向,其显示效果与UIModalPresentationFullScreen相同

  • UIModalPresentation.currentContext 使用这种方式present VC时,presented VC的宽高取决于presentation context的宽高,并且UIKit会寻找属性definesPresentationContext为YES的VC作为presentation context,具体的寻找方式 。当此次presentation完成之后,presentation context及其子VC都将被暂时移出当前的UI栈。

  • UIModalPresentation.custom 自定义模式,需要实现UIViewControllerTransitioningDelegate的相关方法,并将presented VC的transitioningDelegate 设置为实现了UIViewControllerTransitioningDelegate协议的对象。

  • UIModalPresentation.overFullScreen 与UIModalPresentationFullScreen的唯一区别在于,UIWindow下除了presented VC,还有其他正常的VC层级关系。也就是说该模式下,UIKit以rootViewController为presentation context,但presentation完成之后不会将rootViewController移出当前的UI栈。

  • UIModalPresentation.overCurrentContext 寻找presentation context的方式与UIModalPresentationCurrentContext相同,所不同的是presentation完成之后,不会将context及其子VC移出当前UI栈。但是,这种方式只适用于transition style为UIModalTransitionStyleCoverVertical的情况(UIKit默认就是这种transition style)。其他transition style下使用这种方式将会触发异常。

  • UIModalPresentation.popover 主要配合UIPopoverPresentationController使用,做一个气泡弹窗视图

  • UIModalPresentation.automatic iOS 13出来的新样式,有层次结构那种

到这里我们对对modalPresentationStyle有了一个深入的了解,接着看问题3

问题3

从之前的分析,我门知道,presentingViewController(A页面)的viewwilldisappearviewDidDisappear不调用,是和modalPresentationStyleUIModalPresentation.custom有关

那么现在我们需要presentingViewController(A页面)从试图层级上移除,来触发viewwilldisappearviewDidDisappear的调用

通过各种研究官方文档stackoverflow

最终找到了答案

image.png

完美解决了以上所有问题

总结

  • A present B,A,B的viewWillAppearviewDidAppearviewWillDisappearviewDidDisappear调用顺序是怎样的?
🍀🍀🍀 开始显示 PresentedVC 🍀🍀🍀
PresentingVC viewWillDisappear(_:)
PresentedVC viewWillAppear(_:)
PresentedVC viewDidAppear(_:)
PresentingVC viewDidDisappear(_:)
  • A页面的viewWillDisappearviewDidDisappear在什么情况会调用,什么情况不会调用,为什么?

根据modalPresentationStyle的类型决定,如果不需要从视图层级上移除的就不会掉用,反之

  • 在自定义了转场之后(modalPresentationStyle设置为custom),怎么让A页面的viewWillDisappearviewDidDisappear调用?

自定义UIPresentationController,并将shouldRemovePresentersView返回true

本文测试Demo