更好的閱讀體驗(yàn),請到個(gè)人博客閱讀: iOS中的系統(tǒng)轉(zhuǎn)場
請忽略標(biāo)題,??,本文記錄的是對下圖所示的Kind, Presentation, Transition的理解:
目錄如下:
<h1 id="size-classes">Size Classes</h1>
開始之前,先來了解一下Size Classes, 從iOS8.0開始引入了Size Classes的概念來對屏幕進(jìn)行分類:把各個(gè)設(shè)備屏幕以及它們的屏幕旋轉(zhuǎn)狀態(tài)都抽象成屏幕Size的變化,下表是不同尺寸設(shè)備屏幕對Size Classes的約定。
Device | Portrait | Landscape |
---|---|---|
iPad (all)</br>iPad Mini | Vertical size class: Regular</br>Horizontal size class: Regular | Vertical size class: Regular</br>Horizontal size class: Regular |
iPhone 6 Plus | Vertical size class: Regular</br>Horizontal size class: Compact | Vertical size class: Compact</br>Horizontal size class: Regular |
iPhone 6 | Vertical size class: Regular</br>Horizontal size class: Compact | Vertical size class: Compact</br>Horizontal size class: Compact |
iPhone 5s</br>iPhone 5c</br>iPhone 5 | Vertical size class: Regular</br>Horizontal size class: Compact | Vertical size class: Compact</br>Horizontal size class: Compact |
iPhone 4s | Vertical size class: Regular</br>Horizontal size class: Compact | Vertical size class: Compact</br>Horizontal size class: Compact |
寬(正常,任意, 緊湊),高(正常,任意, 緊湊),3 x 3 共 9 種 Size,每種 Size 都可以設(shè)置特定的一套布局,如果不特殊指定,默認(rèn)是在(寬任意,高任意)模式下設(shè)置,且其他 8 種布局繼承它。對于Size Classes,可以看看博客: iOS8 Size Classes初探
<h1 id="presentation-kind">Kind-轉(zhuǎn)場類型</h1>
先說明一下兩個(gè)名詞,假如view controller A模態(tài)展示了view controller B,則
- presenting view controller, 是上述場景中的A view controller, 它會(huì)負(fù)責(zé)展示另外一個(gè)view controller;
- presented view controller, 是上述場景中的B view controller,它是由其它控制器展示出來的view controller。
下圖是Xcode8.0轉(zhuǎn)場Kind的設(shè)置選項(xiàng),可以看到系統(tǒng)默認(rèn)的類型有四種,Show, Show Detail, Present Modally, Present As Popover。
<h2 id="presentation-kind-show">Show</h2>
Presents a view controller in a primary context.
func show(_ vc: UIViewController, sender: Any?)
使用該方法可以將display view controller從presenting that view controller on screen的過程中解耦,ViewController不需要知道它是嵌入到UINavigationController中或者是UISplitViewController中,都可以調(diào)用此方法來展示到屏幕上。UISplitViewController和UINavigationController重寫了該方法,使其能夠根據(jù)自身設(shè)計(jì)來處理展現(xiàn)過程。例如,UINavigationController重寫了該方法,并使用該方法來push viewcontroller到navigation stack中。
該方法的默認(rèn)實(shí)現(xiàn)會(huì)調(diào)用targetViewControllerForAction:sender:在view controller層級中找到重寫該方法的對象,然后調(diào)用該對象的此方法,使其能夠以適當(dāng)?shù)姆绞絹碚宫F(xiàn)view controller。 如果targetViewControllerForAction:sender:方法返回nil,此方法會(huì)使用window的root view controller來模態(tài)顯示vc。
你可以在自定義view controller中重寫該方法來展示vc,你的實(shí)現(xiàn)應(yīng)該在regular和compact環(huán)境中做好適配。
<h2 id="presentation-kind-showdetail">Show Detail</h2>
Presents a view controller in a secondary (or detail)?? context.
func showDetailViewController(_ vc: UIViewController, sender: Any?)
該方法特點(diǎn)同Show類似:使用該方法可以將display view controller從presenting that view controller on screen的過程中解耦,ViewController不需要知道它是嵌入到UINavigationController中或者是UISplitViewController中,都可以調(diào)用此方法來展示到屏幕上。在regular環(huán)境,UISplitViewController重寫了該方法,將vc以detail view controller的方式加載;在compact環(huán)境,split view controller的該方法實(shí)現(xiàn)是調(diào)用show(_:sender:)
方法。
同show(_:sender:)
相同:該方法的默認(rèn)實(shí)現(xiàn)會(huì)調(diào)用targetViewControllerForAction:sender:在view controller層級中找到重寫該方法的對象,然后調(diào)用該對象的此方法,使其能夠以適當(dāng)?shù)姆绞絹碚宫F(xiàn)view controller。 如果targetViewControllerForAction:sender:方法返回nil,此方法會(huì)使用window的root view controller來模態(tài)顯示vc。
你可以在自定義view controller中重載此方法來展示vc,使用此方法來將vc展現(xiàn)在secondary context中,例如,一個(gè)容器view controller可能會(huì)使用此方法來替換第二子控制器,你的實(shí)現(xiàn)應(yīng)該為regular和compact環(huán)境做好適配.
<h2 id="presentation-kind-present-modally">Present Modally</h2>
Presents a view controller modally.
- (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion;
在horizontally regular的環(huán)境,view controller會(huì)以modalPresentationStyle屬性設(shè)定的樣式進(jìn)行展現(xiàn)。在horizontally compact環(huán)境,view controller默認(rèn)會(huì)以全屏的方式進(jìn)行展現(xiàn)。對于viewControllerToPresent關(guān)聯(lián)的presentation controller, 如果你實(shí)現(xiàn)了其adaptive delegate方法,則可以動(dòng)態(tài)修改presentation style。
調(diào)用該方法的對象不一定是處理該presentation的對象。每一個(gè)presentation style會(huì)有不同的規(guī)則來處理其行為。例如,full-screen presentation必須由自身覆蓋了整個(gè)屏幕的view controller來處理。如果調(diào)用該方法的當(dāng)前view controlelr不能滿足該要求,則它會(huì)沿著view controller的層級向上傳遞給最近的父控制器,由其父控制器來負(fù)責(zé)處理或者繼續(xù)轉(zhuǎn)發(fā)該請求。
在展現(xiàn)view controller之前,該方法會(huì)基于presentation style來設(shè)置presented view controller的view大小。對于大多數(shù)的presentation styles,最終視圖會(huì)以presented view controller的modalTransitionStyle屬性設(shè)定的轉(zhuǎn)場樣式動(dòng)畫來顯示到屏幕上。對于自定義的presentations, 視圖會(huì)以presented view controller的transitioning delegate中要求的動(dòng)畫來展示。對于current context presentations,視圖可能會(huì)是以當(dāng)前view controller的transition style來顯示到屏幕上,這種情況稍后會(huì)再次提到。
注意: completion閉包是在presented view controller的viewDidAppear:的方法調(diào)用完成后才會(huì)調(diào)用。
接下來聊聊present modally類型下的presentation style.
<h3 id="presentation-style">Presentation Styles</h3>
view controller的presentation style控制其在屏幕上的顯示。UIKit定義了多種標(biāo)準(zhǔn)的presentation styles, 每一種都有特定的UI展示和用途。當(dāng)然,你也可以自定義presentation styles。當(dāng)設(shè)計(jì)你的app時(shí),根據(jù)當(dāng)前的使用場景選擇最合適的presentaion style, 然后設(shè)置presented view controller的modalPresentationStyle屬性值為對應(yīng)的常量值。
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的枚舉值定義如下:
public enum UIModalPresentationStyle : Int {
case fullScreen
@available(iOS 3.2, *)
case pageSheet
@available(iOS 3.2, *)
case formSheet
@available(iOS 3.2, *)
case currentContext
@available(iOS 7.0, *)
case custom
@available(iOS 8.0, *)
case overFullScreen
@available(iOS 8.0, *)
case overCurrentContext
@available(iOS 8.0, *)
case popover
@available(iOS 7.0, *)
case none
}
由于popover需要進(jìn)行特殊處理,在Xcode中,Presentation處僅有如下圖所示的設(shè)置:
對以上枚舉值進(jìn)行大體分類: full-screen presentation styles, popover style, current context styles, custom presentation styles。其中:
- full-screen presentation styles對應(yīng)枚舉值fullScreen, pageSheet, formSheet, overFullScreen;
- popover style對應(yīng)枚舉值popover;
- current context styles對應(yīng)枚舉值currentContext, overCurrentContext。
先看看full-screen presentation styles。
<h4 id="full-screen-presentation">Full-Screen Presentation Styles</h4>
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會(huì)覆蓋設(shè)備的整個(gè)屏幕,阻止用戶與底部內(nèi)容進(jìn)行交互。在horizontally regular環(huán)境,full screen presentation styles中只有一種類型(UIModalPresentationFullScreen)會(huì)完全覆蓋住底部的內(nèi)容,其余的類型會(huì)結(jié)合透明或者黑色半透明的視圖來使底部的部分內(nèi)容顯示出來。在horizontally compact環(huán)境,所有的full screen presentation styles會(huì)自動(dòng)適配為UIModalPresentationFullScreen類型并覆蓋底部的所有內(nèi)容。
下圖是在horizontally regular環(huán)境下fullScreen, pageSheet, formSheet樣式下的顯示效果。圖中,左上角的綠色view controller會(huì)presents右上角的藍(lán)色view controller,對于某些presentation styles, UIKit會(huì)在兩個(gè)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會(huì)完全覆蓋屏幕。且屬于presenting view controller的視圖會(huì)在presentation completes后移除視圖層級。
注意:當(dāng)你使用UIModalPresentationFullScreen樣式來presenting a view controller的時(shí)候,UIKit會(huì)在轉(zhuǎn)場動(dòng)畫完成后移除底部控制器(所覆蓋的控制器)的視圖,如果你想阻止視圖的移除,則可以使用UIModalPresentationOverFullScreen樣式來替代。當(dāng)presented view controller設(shè)計(jì)有透明區(qū)域以顯示底層內(nèi)容時(shí),你可能會(huì)用到這種樣式。
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環(huán)境,此種presentation style會(huì)部分遮擋底部的內(nèi)容。presented view的寬度會(huì)被設(shè)置成與portrait orientation模式下屏幕的寬度相等,且presented view的高度被設(shè)置成與設(shè)備當(dāng)前模式(portrait 或者 landscape)下屏幕的高度相同。任何未覆蓋到的區(qū)域會(huì)加黑色透明視圖以阻止用戶的交互。在portrait orientations場景,該設(shè)置大體上與fullScreen相同(該樣式下,statusbar上會(huì)覆蓋黑色透明視圖)。在horizontally compact環(huán)境下,該樣式與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環(huán)境下,formSheet這種樣式會(huì)將內(nèi)容布局在屏幕的中間。顯示內(nèi)容的寬高都會(huì)比屏幕尺寸小,并且會(huì)在顯示內(nèi)容下插入黑色透明視圖(dimming view)。如果設(shè)備處于landscape orientation并且彈出了鍵盤,顯示內(nèi)容會(huì)向上移動(dòng)適配以使顯示內(nèi)容仍然可見。所有未被顯示內(nèi)容覆蓋到的區(qū)域都會(huì)被dimmed以阻止用戶與其交互。
在horizontally compact環(huán)境,該選項(xiàng)的效果與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.
當(dāng)你使用這些full-screen presentation styles時(shí),啟動(dòng)這次presentation的view controller必須其自身是覆蓋了整個(gè)屏幕。如果presenting view controller沒有覆蓋整個(gè)屏幕,則UIKit會(huì)沿著view controller的層級一直尋找直到找到滿足該條件的view controller,如果沒有找到,UIKit則會(huì)使用window的root view controller。
<h4 id="popover">The Popover Style</h4>
內(nèi)容見Presenting a View Controller in a Popover
<h4 id="current-context">The Current Context Styles</h4>
使用UIModalPresentationCurrentContext樣式會(huì)使presented view controller覆蓋你在UI中指定的view controller。你通過設(shè)置view controller的definesPresentationContext屬性值為YES來使其成為被覆蓋的對象。下圖演示的是curren context presentation僅僅覆蓋了split view controller的一個(gè)子控制器。

注意:使用該樣式時(shí),對于未被presented view覆蓋的區(qū)域,用戶仍然可以與UI元素(例如UIButton)進(jìn)行交互。
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.
該樣式會(huì)將內(nèi)容顯示在屬性值definesPresentationContext為true的view controller之上。UIKit會(huì)沿著view controller的層級向上尋找定義了presentation context的view controller。屬于presenting view controller的視圖會(huì)在presentation completes后移除。
當(dāng)在一個(gè)popover中來presenting view controller時(shí),只有當(dāng)transition style是coverVertical時(shí)才支持此種樣式,使用其它類型的transition style會(huì)導(dǎo)致crash(經(jīng)過測試,只有當(dāng)transition styles為partial curl時(shí)才會(huì)crash,不過小心為妙)。當(dāng)parent view controller不在popover中時(shí),你可以使用除partial curl外的其它transition styles。
當(dāng)轉(zhuǎn)場結(jié)束時(shí),屬于presented view controller的視圖會(huì)被移除,你可以使用UIModalPresentationOverCurrentContext樣式來替代以避免被移除,你可能會(huì)在當(dāng)presented view controller有透明區(qū)域使底部內(nèi)容顯示出來時(shí)使用此樣式。
定義成為presentation context的view controller, 也可以在其內(nèi)定義presentation時(shí)使用的轉(zhuǎn)場動(dòng)畫。通常情況下,UIKit會(huì)使用presented view controller的modalTransitionStyle屬性定義的動(dòng)畫將presented view controller顯示到屏幕上。如果presentation context view controller的屬性providesPresentationContextTransitionStyle值為YES,則UIKit會(huì)使用該view controller的modalTransitionStyle屬性值來執(zhí)行動(dòng)畫。
當(dāng)轉(zhuǎn)換到horizontally compact環(huán)境時(shí),current context樣式會(huì)適配為UIModalPresentationFullScreen樣式。如果要想改變這種適配行為,可以使用adaptive presentation delegate來指定另一種presentation style或者更換view controller。
<h4 id="custom-presentation">Custom Presentation Styles</h4>
UIModalPresentationCustom允許你使用自定義的樣式來展示view controller。創(chuàng)建自定義的樣式需要你子類化UIPresentationController并且使用其方法來將自定義視圖動(dòng)畫顯示到屏幕,定義其大小和設(shè)置presente view controller的大小。
更多信息參考: Creating Custom Presentations
<h3 id="transiton-style">Transition Styles</h3>
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時(shí)所用的動(dòng)畫類型。對于系統(tǒng)內(nèi)置的transition styles,你可以通過對presented view controller的modalTransitionStyle屬性賦值來指定樣式。當(dāng)你顯示view controller時(shí),UIKit屬性值對應(yīng)的動(dòng)畫。例如,下圖演示了系統(tǒng)UIModalTransitionStyleCoverVertical樣式對應(yīng)的顯示動(dòng)畫。view controller B從不可見->往上滑進(jìn)屏幕->覆蓋view controller A。當(dāng)view controller B退場時(shí),它會(huì)向下滑出屏幕以顯示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支持的系統(tǒng)轉(zhuǎn)場類型如下圖所示:
經(jīng)過測試,Partial Curl轉(zhuǎn)場只有在Presentation為Full Screen時(shí)才會(huì)有效,其余presentation styles會(huì)導(dǎo)致crash,不知道是我使用的方式不對或者是其它原因,請指教。
<h2 id="presenting-popover">Presenting a View Controller in a Popover</h2>
同上,這種方式也是使用api:
- (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion;
來展現(xiàn)視圖。與上不同的是,popover的展現(xiàn)方式還需要額外的配置。在設(shè)置 model presentation style值為UIModalPresentationPopover之后,配置如下與popover相關(guān)的屬性值:
- 設(shè)置presented view controller的preferredContentSize,將視圖大小設(shè)置為期望的大小;
- 從presented view controller的popoverPresentationController屬性中獲取關(guān)聯(lián)的UIPopoverPresentationController對象,然后設(shè)置該對象的錨點(diǎn)。設(shè)置錨點(diǎn)可從如下兩種方式中任選一種:
- 設(shè)置barButtonItem屬性值為一個(gè)bar button item;
- 設(shè)置sourceView和sourceRect屬性值為視圖中的特定區(qū)域;
你可以使用UIPopoverPresentationController對象來修改popover最終繪制出來的效果。popover presentation controller同樣也支持使用一個(gè)delegate對象來監(jiān)聽presentation過程以做出對應(yīng)的響應(yīng)。例如,你可以使用該delegate對象來監(jiān)聽popover的appears, disappears, 或者repositioned on the screen事件,以做出對應(yīng)的響應(yīng)。關(guān)于該對象的更多信息,請參考: UIPopoverPresentationController Class Reference
UIModalPresentationPopover樣式會(huì)以浮層的形式顯示view controller.Popovers可用來顯示一些額外信息,或者,是當(dāng)前聚焦或者選中對象關(guān)聯(lián)的對象列表。在horizontally regular環(huán)境,彈出的浮層只會(huì)覆蓋屏幕的一部分,如下圖所示。在horizontally compact環(huán)境,popovers會(huì)默認(rèn)適配為UIModalPresentationOverFullScreen樣式。在浮層以外的區(qū)域點(diǎn)擊則會(huì)自動(dòng)使浮層消失。

由于popovers在horizontally compact環(huán)境下會(huì)自動(dòng)適配為full-screen presentations,你需要編輯你的浮層代碼以處理這種適配。在full-screen模式下,你需要一種方式來使presented popover消失。你可以添加一個(gè)按鈕,將popover嵌入到可dismissible的容器視圖控制器中,或者是修改該適配行為。