本文是我的 WWDC 2014 筆記 中的一篇,涉及的 Session 有
iOS 8 和 OS X 10.10 中一個(gè)被強(qiáng)調(diào)了多次的主題就是大一統(tǒng),Apple 希望通過 Hand-off 和各種體驗(yàn)的無縫切換和集成將用戶黏在由 Apple 設(shè)備構(gòu)成的生態(tài)圈中。而對(duì)開發(fā)者而言,今年除了 Swift 的一個(gè)大主題也是平臺(tái)的統(tǒng)一。在 What's New in Cocoa Touch 的 Seesion 一開始,UIKit 的工程師 Luke 就指出了 iOS 8 SDK 的最重要的關(guān)鍵字就是自適應(yīng) (adaptivity)。這是一個(gè)很激動(dòng)人心的詞,首先自適應(yīng)是一種設(shè)計(jì)哲學(xué),盡量使事情保持簡單,我們便可從中擢取優(yōu)雅;另一方面,可能這也是 Apple 不得不做的轉(zhuǎn)變。隨著傳說中的更大屏和超大屏的 iPhone 6 的到來,開發(fā)者在為 iOS 進(jìn)行開發(fā)的時(shí)候似乎也開始面臨著和安卓一樣的設(shè)備尺寸的碎片化的問題。而 iOS 8 所著重希望解決的,就是這一問題。
Size Classes
首先最值得一說的是,iOS 8 應(yīng)用在界面設(shè)計(jì)時(shí),迎來了一個(gè)可以說是革命性的變化 - Size Classes。
基本概念
在 iPad 和 iPhone 5 出現(xiàn)之前,iOS 設(shè)備就只有一種尺寸。我們?cè)谧銎聊贿m配時(shí)需要考慮的僅僅有設(shè)備方向而已。而很多應(yīng)用并不支持轉(zhuǎn)向,這樣的話就完全沒有屏幕適配的工作了。隨著 iPad 和 iPhone 5,以及接下來的 iPhone 6 的推出,屏幕尺寸也變成了需要考慮的對(duì)象。在 iOS 7 之前,為一個(gè)應(yīng)用,特別是 universal 的應(yīng)用制作 UI 時(shí),我們總會(huì)首先想我們的目標(biāo)設(shè)備的長寬各是多少,方向變換以后布局又應(yīng)該怎么改變,然后進(jìn)行布局。iOS 6 引入了 Auto Layout 來幫助開發(fā)者使用約束進(jìn)行布局,這使得在某些情況下我們不再需要考慮尺寸,而可以專注于使用約束來規(guī)定位置。
既然我們有了 Auto Layout,那么其實(shí)通過約束來指定視圖的位置和尺寸是沒有什么問題的了,從這個(gè)方面來說,屏幕的具體的尺寸和方向已經(jīng)不那么重要了。但是實(shí)戰(zhàn)中這還不夠,Auto Layout 正如其名,只是一個(gè)根據(jù)約束來進(jìn)行布局的方案,而在對(duì)應(yīng)不同設(shè)備的具體情況下的體驗(yàn)上還有欠缺。一個(gè)最明顯的問題是它不能根據(jù)設(shè)備類型來確定不同的交互體驗(yàn)。很多時(shí)候你還是需要判斷設(shè)備到底是 iPhone 還是 iPad,以及現(xiàn)在的設(shè)備方向究竟是豎直還是水平來做出判斷。這樣的話我們還是難以徹底擺脫對(duì)于設(shè)備的判斷和依賴,而之后如果有新的尺寸和設(shè)備出現(xiàn)的話,這種依賴關(guān)系顯然顯得十分脆弱的(想想要是有 iWatch 的話..)。
所以在 iOS 8 里,Apple 從最初的設(shè)計(jì)哲學(xué)上將原來的方式推翻了,并引入了一整套新的理念,來適應(yīng)設(shè)備不斷的發(fā)展。這就是 Size Classes。
不再根據(jù)設(shè)備屏幕的具體尺寸來進(jìn)行區(qū)分,而是通過它們的感官表現(xiàn),將其分為普通 (Regular) 和緊密 (Compact) 兩個(gè)種類 (class)。開發(fā)者便可以無視具體的尺寸,而是對(duì)這這兩類和它們的組合進(jìn)行適配。這樣不論在設(shè)計(jì)時(shí)還是代碼上,我們都可以不再受限于具體的尺寸,而是變成遵循尺寸的視覺感官來進(jìn)行適配。
簡單來說,現(xiàn)在的 iPad 不論橫屏還是豎屏,兩個(gè)方向均是 Regular 的;而對(duì)于 iPhone,豎屏?xí)r豎直方向?yàn)?Regular,水平方向是 Compact,而在橫屏?xí)r兩個(gè)方向都是 Compact。要注意的是,這里和談到的設(shè)備和方向,都僅僅只是為了給大家一個(gè)直觀的印象。相信隨著設(shè)備的變化,這個(gè)分類也會(huì)發(fā)生變動(dòng)和更新。Size Classes 的設(shè)計(jì)哲學(xué)就是尺寸無關(guān),在實(shí)際中我們也應(yīng)該盡量把具體的尺寸拋開腦后,而去盡快習(xí)慣和適應(yīng)新的體系。
UITraitCollection 和 UITraitEnvironment
為了表征 Size Classes,Apple 在 iOS 8 中引入了一個(gè)新的類,UITraitCollection 。這個(gè)類封裝了像水平和豎直方向的 Size Class 等信息。iOS 8 的 UIKit 中大多數(shù) UI 的基礎(chǔ)類 (包括 UIScreen ,UIWindow ,UIViewController 和 UIView ) 都實(shí)現(xiàn)了 UITraitEnvironment 這個(gè)接口,通過其中的 traitCollection 這個(gè)屬性,我們可以拿到對(duì)應(yīng)的 UITraitCollection 對(duì)象,從而得知當(dāng)前的 Size Class,并進(jìn)一步確定界面的布局。
和 UIKit 中的響應(yīng)者鏈正好相反,traitCollection 將會(huì)在 view hierarchy 中自上而下地進(jìn)行傳遞。對(duì)于沒有指定 traitCollection 的 UI 部件,將使用其父節(jié)點(diǎn)的 traitCollection 。這在布局包含 childViewController 的界面的時(shí)候會(huì)相當(dāng)有用。在 UITraitEnvironment 這個(gè)接口中另一個(gè)非常有用的是 -traitCollectionDidChange: 。在 traitCollection 發(fā)生變化時(shí),這個(gè)方法將被調(diào)用。在實(shí)際操作時(shí),我們往往會(huì)在 ViewController 中重寫 -traitCollectionDidChange: 或者 -willTransitionToTraitCollection:withTransitionCoordinator: 方法 (對(duì)于 ViewController 來說的話,后者也許是更好的選擇,因?yàn)樘峁┝宿D(zhuǎn)場上下文方便進(jìn)行動(dòng)畫;但是對(duì)于普通的 View 來說就只有前面一個(gè)方法了),然后在其中對(duì)當(dāng)前的 traitCollection 進(jìn)行判斷,并進(jìn)行重新布局以及動(dòng)畫。代碼看起來大概會(huì)是這個(gè)樣子:
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
{
[super willTransitionToTraitCollection:newCollection
withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
//To Do: modify something for compact vertical size
} else {
//To Do: modify something for other vertical size
}
[self.view setNeedsLayout];
} completion:nil];
}
在兩個(gè) To Do 中,我們應(yīng)該刪除或者添加或者更改不同條件下的 Auto Layout 約束 (當(dāng)然,你也可以干其他任何你想做的事情),然后調(diào)用 -setNeedsLayout 來在上下文中觸發(fā)轉(zhuǎn)移動(dòng)畫。如果你堅(jiān)持用代碼來處理的話,可能需要面臨對(duì)于不同 Size Classes 來做移除舊的約束和添加新的約束這樣的事情,可以說是很麻煩 (至少我覺得是麻煩的要死)。但是如果我們使用 IB 的話,這些事情和代碼都可以省掉,我們可以非常方便地在 IB 中指定各種 Size Classes 的約束 (稍后會(huì)介紹如何使用 IB 來對(duì)應(yīng) Size Classes)。另外使用 IB 不僅可以節(jié)約成百上千行的布局代碼,更可以從新的 Xcode 和 IB 中得到很多設(shè)計(jì)時(shí)就可以實(shí)時(shí)監(jiān)視,查看并且調(diào)試的特性??梢哉f手寫 UI 和使用 IB 設(shè)計(jì)的時(shí)間消耗和成本差距被進(jìn)一步拉大,并且出現(xiàn)了很多手寫 UI 無法實(shí)現(xiàn),但是 IB 可以不假思索地完成的任務(wù)。從這個(gè)意義上來說,新的 IB 和 Size Classes 系統(tǒng)可以說無情地給手寫代碼判了個(gè)死緩。
另外,新的 API 和體系的引入也同時(shí)給很多我們熟悉的 UIViewController 的有關(guān)旋轉(zhuǎn)的老朋友判了死刑,比如下面這些 API 都棄用了:
@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
- shouldAutomaticallyForwardRotationMethods
現(xiàn)在全部統(tǒng)一到了 viewWillTransitionToSize:withTransitionCoordinator: ,旋轉(zhuǎn)的概念不再被提倡使用。其實(shí)仔細(xì)想想,所謂旋轉(zhuǎn),不過就是一種 Size 的改變而已,我們都被 Apple 騙了好多年,不是么?
Farewell, I will NOT miss you at all.
在 Interface Builder 中使用 Size Classes
第一次接觸 Xcode 6 和打開 IB 的時(shí)候你可能會(huì)驚呼,為什么我的畫布變成正方形了。我在第一天 Keynote 結(jié)束后在 Moscone Center 的食堂里第一次打開的時(shí)候,還滿以為自己找到了 iWatch 方形顯示屏的確鑿證據(jù)。到后來才知道,這是新的 Size Classes 對(duì)應(yīng)的編輯方式。
既然我們不需要關(guān)心實(shí)際的具體尺寸,那么我們也就完全沒有必要在 IB 中使用像 3.5/4 寸的 iPhone 或是 10 寸的 iPad 來分開對(duì)界面進(jìn)行編輯。使用一個(gè)通用的具有 "代表" 性質(zhì)的尺寸在新體系中確實(shí)更不容易使人迷惑。
在現(xiàn)在的 IB 界面的正下方,你可以看到一個(gè) wAny hAny 的按鈕 (因?yàn)榻衲?NDA 的一個(gè)明確限制是不能發(fā)相關(guān)軟件截圖,雖然其實(shí)可能沒什么太大問題,但是還是尊重 license 比較好),這代表現(xiàn)在的 IB 是對(duì)應(yīng)任意高度和任意寬度的。點(diǎn)擊后便可以選擇需要為哪種 Size Class 進(jìn)行編輯。默認(rèn)情況在 Any Any 下的修改會(huì)對(duì)任意設(shè)備和任意方向生效,而如果先進(jìn)行選擇后再進(jìn)行編輯,就表示編輯只對(duì)選中的設(shè)定生效。這樣我們就很容易在同一個(gè) storyboard 文件里對(duì)不同的設(shè)備進(jìn)行適配:按照設(shè)備需要添加或者編輯某些約束,或者是在特定尺寸下隱藏某些 view (使用 Attribute Inspector 里的 Installed 選框的加號(hào)添加)。這使得使用 IB 制作通用程序變簡單了,我們不再需要為 iPhone 和 iPad 準(zhǔn)備兩套 storyboard 了。
可以發(fā)揮的想象空間實(shí)在太大,一套界面布局通吃所有設(shè)備的畫面太美好,我都不敢想。
Size Classes 和 Image Asset 及 UIAppearence
Image Asset 里也加入了對(duì) Size Classes 的支持,也就是說,我們可以對(duì)不同的 Size Class 指定不同的圖片了。在 Image Asset 的編輯面板中選擇某張圖片,Inspector 里現(xiàn)在多了一個(gè) Width 和 Height 的組合,添加我們需要對(duì)應(yīng)的 Size Class, 然后把合適的圖拖上去,這樣在運(yùn)行時(shí) SDK 就將從中挑選對(duì)應(yīng)的 Size 的圖進(jìn)行替換了。不僅如此,在 IB 中我們也可以選擇對(duì)應(yīng)的 size 來直接在編輯時(shí)查看變化(新的 Xcode 和 IB 添加了非常多編輯時(shí)的可視化特性,關(guān)于這方面我有計(jì)劃單獨(dú)寫一篇可視化開發(fā)的文章進(jìn)行說明)。
這個(gè)特性一個(gè)最有用的地方在于對(duì)于不同屏幕尺寸可能我們需要的圖像尺寸也有所不同。比如我們希望在 iPhone 豎屏或者 iPad 時(shí)的按鈕高一些,而 iPhone 橫屏?xí)r由于屏幕高度實(shí)在有限,我們希望得到一個(gè)扁一些的按鈕。對(duì)于純色按鈕我們可以通過簡單的約束和拉伸來實(shí)現(xiàn),但是對(duì)于有圖案的按鈕,我們之前可能就需要在 VC 里寫一些臟代碼來處理了。現(xiàn)在,只需要指定好特定的 Image Asset,然后配置合適的 (比如不含有尺寸限制) 約束,我們就可以一行代碼不寫,就完成這樣復(fù)雜的各個(gè)機(jī)型和方向的適配了。
實(shí)際做起來實(shí)在是太簡單了..但拿個(gè) demo 說明一下吧,比如下面這個(gè)實(shí)現(xiàn)了豎直方向 Compact 的時(shí)候?qū)⑿δ槗Q成哭臉 -- 當(dāng)然了,一行代碼都不需要。
另外,在 iOS 7 中 UIImage 添加了一個(gè) renderingMode 屬性。我們可以使用 imageWithRenderingMode: 并傳入一個(gè)合適的 UIImageRenderingMode 來指定這個(gè) image 要不要以 Template 的方式進(jìn)行渲染。在新的 Xcode 中,我們可以直接在 Image Asset 里的 Render As 選項(xiàng)來指定是不是需要作為 template 使用。而相應(yīng)的,在 UIApperance 中,Apple 也為我們對(duì)于 Size Classes 添加了相應(yīng)的方法。使用 +appearanceForTraitCollection: 方法,我們就可以針對(duì)不同 trait 下的應(yīng)用的 apperance 進(jìn)行很簡單的設(shè)定。比如在上面的例子中,我們想讓笑臉是綠色,而哭臉是紅色的話,不要太簡單。首先在 Image Asset 里的渲染選項(xiàng)設(shè)置為 Template Image ,然后直接在 AppDelegate 里加上這樣兩行:
UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Compact)).tintColor = UIColor.redColor()
UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Regular)).tintColor = UIColor.greenColor()
完成,只不過拖拖鼠標(biāo),兩行簡單的代碼,隨后還能隨喜換色,果然是大快所有人心的大好事。
UIViewController 的表現(xiàn)方式
UISplitViewController
在用 Regular 和 Compact 統(tǒng)一了 IB 界面設(shè)計(jì)之后,Apple 的工程師可能發(fā)現(xiàn)了一個(gè)讓人兩難的歷史問題,這就是 UISplitViewController 。一直做 iPhone 而沒太涉及 iPad 的童鞋可能對(duì)著這個(gè)類不是很熟悉,因?yàn)樗鼈兪?iPad Only 的。iPad 推出時(shí)為了適應(yīng)突然變大的屏幕,并且遠(yuǎn)離 "放大版 iTouch" 的詬病,Apple 為 iPad 專門設(shè)計(jì)了這個(gè)主從關(guān)系的 ViewControlle容器。事實(shí)也證明了這個(gè)設(shè)計(jì)在 iPad 上確實(shí)是被廣泛使用,是非常成功的。
現(xiàn)在的問題是,如果我們只有一套 UI 畫布的話,我們要怎么在這個(gè)單一的畫布上處理和表現(xiàn)這個(gè) iPad Only 的類呢?
答案是,讓它在 iPhone 上也能用就行了。沒錯(cuò),現(xiàn)在你可以直接在 iPhone 上使用 SplitViewController 了。在 Regular 的寬度時(shí),它保持原來的特性,在 DetailViewController 中顯示內(nèi)容,這是毫無疑問的。而在 Compact 中,我們第一想法就是以 push 的表現(xiàn)形式展示。在以前,我們可能需要寫不少代碼來處理這些事情,比如在 AppDelegate 中就在一開始判斷設(shè)備是不是 iPad,然后為應(yīng)用設(shè)定兩套完全不同的導(dǎo)航:一套基于 UINavigationController ,另一套基于 UISplitViewController 。而現(xiàn)在我們只需要一套 UISplitViewController ,并將它的 MasterViewController 設(shè)定為一個(gè) navgationController 就可以輕松搞定所有情況了。
也許你會(huì)想,即使這樣,我是不是還是需要判斷設(shè)備是不是 iPad,或者現(xiàn)在的話是判斷 Size Class 是不是 Compact,來決定是要做的到底是 navVC 的 push 還是改變 splitVC 的 viewControllers。其實(shí)不用,我們現(xiàn)在可以無痛地不加判斷,直接用統(tǒng)一的方式來完成兩種表現(xiàn)方式。這其中的奧妙在于我們不需要使用 (事實(shí)上 iOS 8 后 Apple 也不再提倡使用) UINavigationController 的 pushViewController:animated: 方法了 (又一個(gè)老朋友要和我們說再見了)。其實(shí)雖然很常用,但是這個(gè)方法是一直受到社區(qū)的議論的:因?yàn)檎沁@個(gè)方法的存在使得 ViewController 的耦合特性上了一個(gè)檔次。在某個(gè) ViewController 中這個(gè)方法的存在時(shí),就意味著我們需要確保當(dāng)前的 ViewController 必須處于一個(gè)導(dǎo)航棧的上下文中,這是完全和上下文耦合的一種方式 (雖然我們也可以很蛋疼地用判斷 navController 是不是 nil 來繞開,但是畢竟真的很丑,不是么)。
我們現(xiàn)在有了新的展示 viewController 的方法,-showViewController:sender: 以及 -showDetailViewController:sender: 。調(diào)用這兩個(gè)方法時(shí),將順著包括調(diào)用 vc 自身的響應(yīng)鏈而上,尋找最近的實(shí)現(xiàn)了這個(gè)方法的 ViewController 來執(zhí)行相應(yīng)代碼。在 iOS SDK 的默認(rèn)實(shí)現(xiàn)中,在 UISplitViewController 這樣的容器類中,已經(jīng)有這兩個(gè)方法的實(shí)現(xiàn)方式,而 UINavigationController 也實(shí)現(xiàn)了 -showViewController:sender: 的版本。對(duì)于在 navController 棧中的 vc,會(huì)調(diào)用 push 方式進(jìn)行展示,而對(duì) splitVC,showViewController:sender: 將在 MasterViewController 中進(jìn)行 push。而 showDetailViewController:sender: 將根據(jù)水平方向的 Size 的情況進(jìn)行選擇:對(duì)于 Regular 的情況,將在 DetailViewController 中顯示新的 vc,而對(duì)于 Compact 的情況,將由所在上下文情況發(fā)回給下級(jí)的 navController 或者是直接以 modal 的方式展現(xiàn)。關(guān)于這部分的具體內(nèi)容,可以仔細(xì)看看這個(gè)示例項(xiàng)目和相關(guān)的文檔 (beta版)。
這么設(shè)計(jì)的好處是顯而易見的,首先是解除了原來的耦合,使得我們的 ViewController 可以不被局限于導(dǎo)航控制器上下文中;另外,這幾個(gè)方法都是公開的,也就是說我們的 ViewController 可以實(shí)現(xiàn)這兩個(gè)方法,截?cái)囗憫?yīng)鏈的響應(yīng),并實(shí)現(xiàn)我們自己的呈現(xiàn)方式。這在自定義 Container Controller 的時(shí)候會(huì)非常有用。
UIPresentationController
iOS 7 中加入了一套實(shí)現(xiàn)非常漂亮的自定義轉(zhuǎn)場動(dòng)畫的方法 (如果你還不知道或者不記得了,可以看看我去年的這篇筆記)。Apple 在解耦和重用上的努力確實(shí)令人驚嘆。而今年,順著自適應(yīng)和平臺(tái)開發(fā)統(tǒng)一的東風(fēng),在呈現(xiàn) ViewController 的方式上 Apple 也做出了從 iOS SDK 誕生以來最大的改變。iOS 8 中新加入了一個(gè)非常重要的類 UIPresentationController ,這個(gè) NSObject 的子類將用來管理所有的 ViewController 的呈現(xiàn)。在實(shí)現(xiàn)方式上,這個(gè)類和去年的自定義轉(zhuǎn)場的幾個(gè)類一樣,是完全解耦合的。而 Apple 也在自己的各種 viewController 呈現(xiàn)上完全統(tǒng)一地使用了這個(gè)類。
再見 UIPopoverController
和 SplitViewController 類似,UIPopoverController 原來也只是 iPad 使用的,現(xiàn)在 iPhone 上也將適用。準(zhǔn)確地說,現(xiàn)在我們不再使用 UIPopoverController 這個(gè)類 (雖然現(xiàn)在文檔還沒有將其標(biāo)為 deprecated,但是估計(jì)也是遲早的事兒了),而是改用一個(gè)新的類 UIPopoverPresentationController 。這是 UIPresentationController 的子類,專門用來負(fù)責(zé)呈現(xiàn)以 popover 的形式呈現(xiàn)內(nèi)容,是 iOS 8 中用來替代原有的 UIPopoverController 的類。
比起原來的類,新的方式有什么優(yōu)點(diǎn)呢?最大的優(yōu)勢是自適應(yīng),這和 UISplitViewController 在 iOS 8 下的表現(xiàn)是類似的。在 Compact 的寬度條件下,UIPopoverPresentationController 的呈現(xiàn)將會(huì)直接變成 modal 出來。這樣我們基本就不再需要去判斷 iPhone 還是 iPad (其實(shí)相關(guān)的判定方法也已經(jīng)被標(biāo)記成棄用了),就可以對(duì)應(yīng)不同的設(shè)備了。以前我們可能要寫類似這樣的代碼:
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
let popOverController = UIPopoverController(contentViewController: nextVC)
popOverController.presentPopoverFromRect(aRect, inView: self.view, permittedArrowDirections: .Any, animated: true)
} else {
presentViewController(nextVC, animated: true, completion: nil)
}
而現(xiàn)在需要做的是:
nextVC.modalPresentationStyle = .Popover
let popover = nextVC.popoverPresentationController
popover.sourceRect = aRect
popover.permittedArrowDirections = .Any
presentViewController(nextVC, animated: true, completion: nil)
沒有可惡的條件判斷,一切配置井井有條,可讀性也非常好。
除了自適應(yīng)之外,新方式的另一個(gè)優(yōu)點(diǎn)是非常容易自定義。我們可以通過繼承 UIPopoverPresentationController 來實(shí)現(xiàn)我們自己想要的呈現(xiàn)方式。其實(shí)更準(zhǔn)確地說,我們應(yīng)該繼承的是 UIPresentationController ,主要通過實(shí)現(xiàn) -presentationTransitionWillBegin 和 -presentationTransitionDidEnd: 來自定義我們的展示。像以前我們想要實(shí)現(xiàn)只占半個(gè)屏幕,后面原來的 view 還可見的 modal,或者是將從下到上的動(dòng)畫改為百葉窗或者漸隱漸現(xiàn),那都是可費(fèi)勁兒的事情。而在 UIPresentationController 的幫助下,一切變得十分自然和簡單。在自己的 UIPresentationController 子類中:
override func presentationTransitionWillBegin() {
let transitionCoordinator = self.presentingViewController.transitionCoordinator()
transitionCoordinator.animateAlongsideTransition({context in
//Do animation here
}, completion: nil)
}
override func presentationTransitionDidEnd(completed: Bool) {
//Do clean here
}
具體的用法和 iOS 7 里的自定義轉(zhuǎn)場很類似,設(shè)定需要進(jìn)行呈現(xiàn)操作的 ViewController 的 transition delegate,在 UIViewControllerTransitioningDelegate 的 -presentationControllerForPresentedViewController:sourceViewController: 方法中使用 -initWithPresentedViewController:presentingViewController: 生成對(duì)應(yīng)的 UIPresentationController 子類對(duì)象返回給 SDK,然后就可以喝茶看戲了。
再見 UIAlertView, 再見 UIActionSheet
自適應(yīng)和 UIPresentationController 給我們帶來的另一個(gè)大變化是 UIAlertView 和 UIActionSheet 這兩個(gè)類的消亡 (好吧其實(shí)算不上消亡,棄用而已)?,F(xiàn)在,Alert 和 ActionSheet 的呈現(xiàn)也通過 UIPresentationController 來實(shí)現(xiàn)。原來在沒有 Size Class 和需要處理旋轉(zhuǎn)的黑暗年代 (抱歉在這里用了這個(gè)詞,但是我真的一點(diǎn)也不懷念那段處理設(shè)備旋轉(zhuǎn)的時(shí)光) 里,把這兩個(gè) view 顯示出來其實(shí)幕后是一堆惡心的事情:創(chuàng)建新的 window,處理新 window 的大小和方向,然后將 alert 或者 action sheet 按合適的大小和方向加到窗口上,然后還要考慮處理轉(zhuǎn)向,最后顯示出來。雖然 Apple 幫我們做了這些事情,但是輪到我們使用時(shí),往往它們也只能滿足最基本的需求。在適配 iPhone 和 iPad 時(shí),UIAlertView 還好,但是對(duì)于 UIActionSheet 我們往往又得進(jìn)行不同處理,來選擇是不是需要 popover。
另外一個(gè)要命的地方是因?yàn)檫@兩個(gè)類是 iOS 2.0 開始就存在的爺爺級(jí)的類了,而最近一直也沒什么大的更新,設(shè)計(jì)模式上還使用的是傳統(tǒng)的 delegate 那一套東西。實(shí)際上對(duì)于這種很輕很明確的使用邏輯,block handler 才是最好的選擇,君不見滿 GitHub 的 block alert view 的代碼,但是沒轍,4.0 才出現(xiàn)的 block 一直由于種種原因,在這兩個(gè)類中一直沒有得到官方的認(rèn)可和使用。
而作為替代品的 UIAlertController 正是為了解決這些問題而出現(xiàn)的,值得注意的是,這是一個(gè) UIViewController 的子類??赡苣銜?huì)問 UIAlertController 對(duì)應(yīng)替代 UIAlertView ,這很好,但是 UIActionSheet 怎么辦呢?哈..答案是也用 UIAlertController ,在 UIAlertController 中有一個(gè) preferredStyle 的屬性,暫時(shí)給我們提供了兩種選擇 ActionSheet 和 Alert 。在實(shí)際使用時(shí),這個(gè)類的 API 還是很簡單的,使用工廠方法創(chuàng)建對(duì)象,進(jìn)行配置后直接 present 出來:
let alert = UIAlertController(title: "Test", message: "Msg", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default) {
[weak alert] action in
print("OK Pressed")
alert!.dismissViewControllerAnimated(true, completion: nil)
}
alert.addAction(okAction)
presentViewController(alert, animated: true, completion: nil)
使用上除了小心循環(huán)引用以外,并沒有太多好說的。在 Alert 上加文本輸入也變得非常簡單了,使用 -addTextFieldWithConfigurationHandler: 每次向其上添加一個(gè)文本輸入,然后在 handler 里拿數(shù)據(jù)就好了。
要記住的是,在幕后,做呈現(xiàn)的還是 UIPresentationController 。
UISearchDisplayController -> UISearchController
最后想簡單提一下在做搜索欄的時(shí)候的同樣類似的改變。在 iOS 8 之前做搜索欄簡直是一件讓人崩潰的事情,而現(xiàn)在我們不再需要討厭的 UISearchDisplayController 了,也沒有莫名其妙的在視圖樹中強(qiáng)制插入 view 了 (如果你做過搜索欄,應(yīng)該知道我在說什么)。這一切在 iOS 8 中也和前面說到的 alert 和 actionSheet 一樣,被一個(gè) UIViewController 的子類 UISearchController 替代了。背后的呈現(xiàn)機(jī)制自然也是 UIPresentationController ,可見新的這個(gè)類在 iOS 8 中的重要性。
總結(jié)
對(duì)于廣大 iOS 開發(fā)者賴以生存的 UIKit 來說,這次最大的變化就是 Size Classes 的引入和新的 Presentation 系統(tǒng)了。在 Keynot 上 Craig 就告訴我們,iOS 8 SDK 將是 iOS 開發(fā)誕生以來最大的一次變革,此言不虛。雖然 iOS 8 SDK 的廣泛使用估計(jì)還有要有個(gè)兩年時(shí)間,但是不同設(shè)備的開發(fā)的 API 的統(tǒng)一這一步已然邁出,這也正是 Apple 之后的發(fā)展方向。正如兩年前的 Auto Layout 正在今天大放光彩一樣,之后 Size Classes 和新的 ViewController 也必將成為日常開發(fā)的主力工具。
程序員嘛,果然需要每年不斷學(xué)習(xí),才能跟上時(shí)代。
|