iOS中的系统转场
本文记录的是对下图所示的Kind, Presentation, Transition的理解:
目录如下:
Size Classes
开始之前,先来了解一下Size Classes, 从iOS8.0开始引入了Size Classes的概念来对屏幕进行分类:把各个设备屏幕以及它们的屏幕旋转状态都抽象成屏幕Size的变化,下表是不同尺寸设备屏幕对Size Classes的约定。
Device | Portrait | Landscape |
---|---|---|
iPad (all)iPad Mini | Vertical size class: RegularHorizontal size class: Regular | Vertical size class: RegularHorizontal size class: Regular |
iPhone 6 Plus | Vertical size class: RegularHorizontal size class: Compact | Vertical size class: CompactHorizontal size class: Regular |
iPhone 6 | Vertical size class: RegularHorizontal size class: Compact | Vertical size class: CompactHorizontal size class: Compact |
iPhone 5siPhone 5ciPhone 5 | Vertical size class: RegularHorizontal size class: Compact | Vertical size class: CompactHorizontal size class: Compact |
iPhone 4s | Vertical size class: RegularHorizontal size class: Compact | Vertical size class: CompactHorizontal size class: Compact |
宽(正常,任意, 紧凑),高(正常,任意, 紧凑),3 x 3 共 9 种 Size,每种 Size 都可以设置特定的一套布局,如果不特殊指定,默认是在(宽任意,高任意)模式下设置,且其他 8 种布局继承它。对于Size Classes,可以看看博客: iOS8 Size Classes初探
Kind-转场类型
先说明一下两个名词,假如view controller A模态展示了view controller B,则
- presenting view controller, 是上述场景中的A view controller, 它会负责展示另外一个view controller;
- presented view controller, 是上述场景中的B view controller,它是由其它控制器展示出来的view controller。
转场Kind的设置选项,系统默认的类型有四种,Show, Show Detail, Present Modally, Present As Popover。
Show
Presents a view controller in a primary context.
1 func show(_ vc: UIViewController, sender: Any?)
使用该方法可以将display view controller从presenting that view controller on screen的过程中解耦,ViewController不需要知道它是嵌入到UINavigationController中或者是UISplitViewController中,都可以调用此方法来展示到屏幕上。UISplitViewController和UINavigationController重写了该方法,使其能够根据自身设计来处理展现过程。例如,UINavigationController重写了该方法,并使用该方法来push viewcontroller到navigation stack中。
该方法的默认实现会调用targetViewControllerForAction:sender:在view controller层级中找到重写该方法的对象,然后调用该对象的此方法,使其能够以适当的方式来展现view controller。 如果targetViewControllerForAction:sender:方法返回nil,此方法会使用window的root view controller来模态显示vc。
你可以在自定义view controller中重写该方法来展示vc,你的实现应该在regular和compact环境中做好适配。
Show Detail
Presents a view controller in a secondary (or detail) context.
1 func showDetailViewController(_ vc: UIViewController, sender: Any?)
该方法特点同Show类似:使用该方法可以将display view controller从presenting that view controller on screen的过程中解耦,ViewController不需要知道它是嵌入到UINavigationController中或者是UISplitViewController中,都可以调用此方法来展示到屏幕上。在regular环境,UISplitViewController重写了该方法,将vc以detail view controller的方式加载;在compact环境,split view controller的该方法实现是调用show(_:sender:)
方法。
同show(_:sender:)
相同:该方法的默认实现会调用targetViewControllerForAction:sender:在view controller层级中找到重写该方法的对象,然后调用该对象的此方法,使其能够以适当的方式来展现view controller。 如果targetViewControllerForAction:sender:方法返回nil,此方法会使用window的root view controller来模态显示vc。
你可以在自定义view controller中重载此方法来展示vc,使用此方法来将vc展现在secondary context中,例如,一个容器view controller可能会使用此方法来替换第二子控制器,你的实现应该为regular和compact环境做好适配.
Present Modally
Presents a view controller modally.
1
2
3 - (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion;
在horizontally regular的环境,view controller会以modalPresentationStyle属性设定的样式进行展现。在horizontally compact环境,view controller默认会以全屏的方式进行展现。对于viewControllerToPresent关联的presentation controller, 如果你实现了其adaptive delegate方法,则可以动态修改presentation style。
调用该方法的对象不一定是处理该presentation的对象。每一个presentation style会有不同的规则来处理其行为。例如,full-screen presentation必须由自身覆盖了整个屏幕的view controller来处理。如果调用该方法的当前view controlelr不能满足该要求,则它会沿着view controller的层级向上传递给最近的父控制器,由其父控制器来负责处理或者继续转发该请求。
在展现view controller之前,该方法会基于presentation style来设置presented view controller的view大小。对于大多数的presentation styles,最终视图会以presented view controller的modalTransitionStyle属性设定的转场样式动画来显示到屏幕上。对于自定义的presentations, 视图会以presented view controller的transitioning delegate中要求的动画来展示。对于current context presentations,视图可能会是以当前view controller的transition style来显示到屏幕上,这种情况稍后会再次提到。
注意: completion闭包是在presented view controller的viewDidAppear:的方法调用完成后才会调用。
接下来聊聊present modally类型下的presentation style.
Presentation Styles
view controller的presentation style控制其在屏幕上的显示。UIKit定义了多种标准的presentation styles, 每一种都有特定的UI展示和用途。当然,你也可以自定义presentation styles。当设计你的app时,根据当前的使用场景选择最合适的presentaion style, 然后设置presented view controller的modalPresentationStyle属性值为对应的常量值。
modalPresentationStyle:
The presentation style determines how a modally presented view controller is displayed onscreen. In a horizontally compact environment, modal view controllers are always presented full-screen. In a horizontally regular environment, there are several different presentation options. For a list of possible presentation styles, and their compatibility with the available transition styles, see the UIModalPresentationStyle constant descriptions.
UIModalPresentationStyle的枚举值定义如下:
1 | public enum UIModalPresentationStyle : Int { |
由于popover需要进行特殊处理,在Xcode中,Presentation处仅有如下图所示的设置:
对以上枚举值进行大体分类: full-screen presentation styles, popover style, current context styles, custom presentation styles。其中:
- full-screen presentation styles对应枚举值fullScreen, pageSheet, formSheet, overFullScreen;
- popover style对应枚举值popover;
- current context styles对应枚举值currentContext, overCurrentContext。
先看看full-screen presentation styles。
Full-Screen Presentation Styles
Full screen presentation styles cover the entire screen, preventing interactions with the underlying content. In a horizontally regular environment, only one of the full-screen styles covers the underlying content completely. The rest incorporate dimming views or transparency to allow portions of the underlying view controller to show through. In a horizontally compact environment, full-screen presentations automatically adapt to the UIModalPresentationFullScreen style and cover all of the underlying content.
full screen presentation styles会覆盖设备的整个屏幕,阻止用户与底部内容进行交互。在horizontally regular环境,full screen presentation styles中只有一种类型(UIModalPresentationFullScreen)会完全覆盖住底部的内容,其余的类型会结合透明或者黑色半透明的视图来使底部的部分内容显示出来。在horizontally compact环境,所有的full screen presentation styles会自动适配为UIModalPresentationFullScreen类型并覆盖底部的所有内容。
下图是在horizontally regular环境下fullScreen, pageSheet, formSheet样式下的显示效果。图中,左上角的绿色view controller会presents右上角的蓝色view controller,对于某些presentation styles, UIKit会在两个view controller之间插入dimming view。
fullScreen
case fullScreen = 0
A presentation style in which the presented view covers the screen. The views belonging to the presenting view controller are removed after the presentation completes.
此种类型的presentation style presented view会完全覆盖屏幕。且属于presenting view controller的视图会在presentation completes后移除视图层级。
注意:当你使用UIModalPresentationFullScreen样式来presenting a view controller的时候,UIKit会在转场动画完成后移除底部控制器(所覆盖的控制器)的视图,如果你想阻止视图的移除,则可以使用UIModalPresentationOverFullScreen样式来替代。当presented view controller设计有透明区域以显示底层内容时,你可能会用到这种样式。
pageSheet
case pageSheet = 1
In a horizontally regular environment, a presentation style that partially covers the underlying content. The presented view's width is set to the width of the screen in a portrait orientation and the the height is set to the height of the screen. Any uncovered areas are dimmed to prevent the user from interacting with them. (In portrait orientations, this option is essentially the same as fullScreen.)
In a horizontally compact environment, this option behaves the same as fullScreen.
在horizontally regular环境,此种presentation style会部分遮挡底部的内容。presented view的宽度会被设置成与portrait orientation模式下屏幕的宽度相等,且presented view的高度被设置成与设备当前模式(portrait 或者 landscape)下屏幕的高度相同。任何未覆盖到的区域会加黑色透明视图以阻止用户的交互。在portrait orientations场景,该设置大体上与fullScreen相同(该样式下,statusbar上会覆盖黑色透明视图)。在horizontally compact环境下,该样式与fullScreen样式完全相同。
formSheet
case formSheet = 2
In a horizontally regular environment, a presentation style that displays the content centered in the screen. The width and height of the content area are smaller than the screen size and a dimming view is placed underneath the content. If the device is in a landscape orientation and the keyboard is visible, the position of the view is adjusted upward so that the view remains visible. All uncovered areas are dimmed to prevent the user from interacting with them.
In a horizontally compact environment, this option behaves the same as fullScreen.
在horizontally regular环境下,formSheet这种样式会将内容布局在屏幕的中间。显示内容的宽高都会比屏幕尺寸小,并且会在显示内容下插入黑色透明视图(dimming view)。如果设备处于landscape orientation并且弹出了键盘,显示内容会向上移动适配以使显示内容仍然可见。所有未被显示内容覆盖到的区域都会被dimmed以阻止用户与其交互。
在horizontally compact环境,该选项的效果与fullScreen的效果完全一致。
When using one of the full-screen presentation styles, the view controller that initiates the presentation must itself cover the entire screen. If the presenting view controller does not cover the screen, UIKit walks up the view controller hierarchy until it finds one that does. If it can’t find an intermediate view controller that fills the screen, UIKit uses the root view controller of the window.
当你使用这些full-screen presentation styles时,启动这次presentation的view controller必须其自身是覆盖了整个屏幕。如果presenting view controller没有覆盖整个屏幕,则UIKit会沿着view controller的层级一直寻找直到找到满足该条件的view controller,如果没有找到,UIKit则会使用window的root view controller。
The Popover Style
内容见Presenting a View Controller in a Popover
The Current Context Styles
使用UIModalPresentationCurrentContext样式会使presented view controller覆盖你在UI中指定的view controller。你通过设置view controller的definesPresentationContext属性值为YES来使其成为被覆盖的对象。下图演示的是curren context presentation仅仅覆盖了split view controller的一个子控制器。
注意:使用该样式时,对于未被presented view覆盖的区域,用户仍然可以与UI元素(例如UIButton)进行交互。
A presentation style where the content is displayed over a view controller’s content whose definesPresentationContext property is true. UIKit may walk up the view controller hierarchy to find a view controller that wants to define the presentation context. The views belonging to the presenting view controller are removed after the presentation completes.
When presenting a view controller in a popover, this presentation style is supported only if the transition style is coverVertical. Attempting to use a different transition style triggers an exception. However, you may use other transition styles (except the partial curl transition) if the parent view controller is not in a popover.
该样式会将内容显示在属性值definesPresentationContext为true的view controller之上。UIKit会沿着view controller的层级向上寻找定义了presentation context的view controller。属于presenting view controller的视图会在presentation completes后移除。
当在一个popover中来presenting view controller时,只有当transition style是coverVertical时才支持此种样式,使用其它类型的transition style会导致crash(经过测试,只有当transition styles为partial curl时才会crash,不过小心为妙)。当parent view controller不在popover中时,你可以使用除partial curl外的其它transition styles。
当转场结束时,属于presented view controller的视图会被移除,你可以使用UIModalPresentationOverCurrentContext样式来替代以避免被移除,你可能会在当presented view controller有透明区域使底部内容显示出来时使用此样式。
定义成为presentation context的view controller, 也可以在其内定义presentation时使用的转场动画。通常情况下,UIKit会使用presented view controller的modalTransitionStyle属性定义的动画将presented view controller显示到屏幕上。如果presentation context view controller的属性providesPresentationContextTransitionStyle值为YES,则UIKit会使用该view controller的modalTransitionStyle属性值来执行动画。
当转换到horizontally compact环境时,current context样式会适配为UIModalPresentationFullScreen样式。如果要想改变这种适配行为,可以使用adaptive presentation delegate来指定另一种presentation style或者更换view controller。
Custom Presentation Styles
UIModalPresentationCustom允许你使用自定义的样式来展示view controller。创建自定义的样式需要你子类化UIPresentationController并且使用其方法来将自定义视图动画显示到屏幕,定义其大小和设置presente view controller的大小。
更多信息参考: Creating Custom Presentations
Transition Styles
Transition styles determine the type of animations used to display a presented view controller. For the built-in transition styles, you assign one of the standard transition styles to the modalTransitionStyle property of the view controller you want to present. When you present the view controller, UIKit creates the animations that correspond to that style. For example, Figure 8-4 illustrates how the standard slide-up transition (UIModalTransitionStyleCoverVertical) animates the view controller onscreen. View controller B starts offscreen and animates up and over the top of view controller A. When view controller B is dismissed, the animation reverses so that B slides down to reveal A.
transition styles决定了显示presented view controller时所用的动画类型。对于系统内置的transition styles,你可以通过对presented view controller的modalTransitionStyle属性赋值来指定样式。当你显示view controller时,UIKit属性值对应的动画。例如,下图演示了系统UIModalTransitionStyleCoverVertical样式对应的显示动画。view controller B从不可见->往上滑进屏幕->覆盖view controller A。当view controller B退场时,它会向下滑出屏幕以显示view controller A。
You can create custom transitions using an animator object and transitioning delegate. The animator object creates the transition animations for placing the view controller onscreen. The transitioning delegate supplies that animator object to UIKit at the appropriate time. For information about how to implement custom transitions, see Customizing the Transition Animations
目前iOS支持的系统转场类型如下图所示:
经过测试,Partial Curl转场只有在Presentation为Full Screen时才会有效,其余presentation styles会导致crash,不知道是我使用的方式不对或者是其它原因,请指教。
Presenting a View Controller in a Popover
同上,这种方式也是使用api:
1 | - (void)presentViewController:(UIViewController *)viewControllerToPresent |
来展现视图。与上不同的是,popover的展现方式还需要额外的配置。在设置 model presentation style值为UIModalPresentationPopover之后,配置如下与popover相关的属性值:
- 设置presented view controller的preferredContentSize,将视图大小设置为期望的大小;
- 从presented view controller的popoverPresentationController属性中获取关联的UIPopoverPresentationController对象,然后设置该对象的锚点。设置锚点可从如下两种方式中任选一种:
- 设置barButtonItem属性值为一个bar button item;
- 设置sourceView和sourceRect属性值为视图中的特定区域;
你可以使用UIPopoverPresentationController对象来修改popover最终绘制出来的效果。popover presentation controller同样也支持使用一个delegate对象来监听presentation过程以做出对应的响应。例如,你可以使用该delegate对象来监听popover的appears, disappears, 或者repositioned on the screen事件,以做出对应的响应。关于该对象的更多信息,请参考: UIPopoverPresentationController Class Reference
UIModalPresentationPopover样式会以浮层的形式显示view controller.Popovers可用来显示一些额外信息,或者,是当前聚焦或者选中对象关联的对象列表。在horizontally regular环境,弹出的浮层只会覆盖屏幕的一部分,如下图所示。在horizontally compact环境,popovers会默认适配为UIModalPresentationOverFullScreen样式。在浮层以外的区域点击则会自动使浮层消失。
由于popovers在horizontally compact环境下会自动适配为full-screen presentations,你需要编辑你的浮层代码以处理这种适配。在full-screen模式下,你需要一种方式来使presented popover消失。你可以添加一个按钮,将popover嵌入到可dismissible的容器视图控制器中,或者是修改该适配行为。