翻譯自“View Controller Programming Guide for iOS”。
1 自適應(yīng)模型
自適應(yīng)界面可以最大程度利用可用空間??勺赃m應(yīng)意味著可以調(diào)整內(nèi)容適配任何iOS設(shè)備。iOS的自適應(yīng)模型支持簡(jiǎn)單但動(dòng)態(tài)的方式來(lái)重新排列內(nèi)容和調(diào)整內(nèi)容尺寸。利用這個(gè)模型,應(yīng)用程序使用很少代碼就能動(dòng)態(tài)適應(yīng)不同的屏幕尺寸(如圖12-1)。
圖12-1 適應(yīng)不同設(shè)備和方向
自動(dòng)布局(Auto Layout)是創(chuàng)建自適應(yīng)界面的一個(gè)重要工具。使用自動(dòng)布局時(shí),定義規(guī)則(稱為約束)管理視圖控制器視圖的布局??梢栽诮缑嫔善髦锌梢暬瘎?chuàng)建規(guī)則,或者通過(guò)代碼創(chuàng)建。當(dāng)父視圖的尺寸變化時(shí),iOS根據(jù)指定的約束,自動(dòng)調(diào)整其它視圖的大小和位置。
自適應(yīng)模型的另一個(gè)重要組成部分是特征(trait)。特征描述了視圖控制器和視圖必須操作的環(huán)境。特征幫助你對(duì)界面做高級(jí)別的決定。
1.1 特征的角色
單獨(dú)的約束不足以管理布局時(shí),視圖控制器有幾個(gè)機(jī)會(huì)做出改變。視圖控制器,視圖和一些其它對(duì)象管理一組特征集,該特征集指定關(guān)聯(lián)該對(duì)象的當(dāng)前環(huán)境。表格12-1描述了特征,以及如何使用它們來(lái)影響用戶界面。
表格12-1 特征
| 特征 | 例子 | 描述 |
|---|---|---|
| horizontalSizeClass | UIUserInterfaceSizeClassCompact | 該特征指定界面的整體寬度。用它做粗粒度級(jí)別的布局決定,例如視圖是否垂直堆放(stacked),并排顯示,同時(shí)隱藏,或者其它方式顯示。 |
| verticalSizeClass | UIUserInterfaceSizeClassRegular | 該特征指定界面的整體高度。如果設(shè)計(jì)要求所有內(nèi)容沒(méi)有滾動(dòng)的適合屏幕,則使用該特征做布局決定。 |
| displayScale | 2.0 | 該特征指定內(nèi)容在視網(wǎng)膜屏(Retina)還是標(biāo)準(zhǔn)分辨率屏幕顯示。使用它(如果需要)做像素級(jí)別的布局決定,或者顯示哪個(gè)版本的圖片。 |
| userInterfaceIdiom | UIUserInterfaceIdiomPhone | 該特征提供向后兼容,并指定應(yīng)用程序在哪個(gè)類型的設(shè)備上運(yùn)行。盡可能避免使用該特征。對(duì)于布局決定,使用水平和垂直的size classes代替。 |
使用特征決定如何顯示用戶界面。在界面生成器中構(gòu)建界面時(shí),使用特征改變顯示的視圖和圖片,或者應(yīng)用不同的約束集。許多UIKit類,比如UIImageAsset,使用指定的特征調(diào)整它們提供的信息。
下面是一些技巧,幫助理解何時(shí)使用不同類型的特征:
- 使用尺寸類(size classes)對(duì)界面做粗粒度的改變。尺寸類變化是添加或移除視圖,添加或移除子視圖控制器,或者改變布局約束的適當(dāng)時(shí)機(jī)。也可以什么不做,讓界面使用現(xiàn)有的布局約束自動(dòng)適應(yīng)。
- 永遠(yuǎn)不要假設(shè)一個(gè)尺寸類匹配視圖的指定寬度或高度。視圖控制器的尺寸類會(huì)因?yàn)楹芏嘣蚋淖?。例如,iPhone上的容器視圖控制器可能讓其中一個(gè)子視圖控制器的水平方向?yàn)槌R?guī),強(qiáng)制它以不同方式顯示內(nèi)容。
- 酌情使用界面生成器為每個(gè)尺寸類指定不同的布局約束。使用界面生成器指定約束比自己添加和移除約束更簡(jiǎn)單。視圖控制器自動(dòng)從故事版中應(yīng)用合適的約束來(lái)處理尺寸類變化。如何為不同尺寸類配置布局約束,請(qǐng)參考“配置故事版處理不同的Size Classes”。
- 避免使用idiom信息決定布局或界面內(nèi)容。iPad和iPhone上運(yùn)行的應(yīng)用程序通常顯示同樣的信息,應(yīng)該使用尺寸類決定布局。
1.2 什么時(shí)候發(fā)生特征和尺寸變化?
特征很少變化,但確實(shí)會(huì)發(fā)生變化。UIKit根據(jù)底層環(huán)境的變化更新視圖控制器的特征。尺寸類特征比顯示比例(display scale)更經(jīng)常變化。idiom特征幾乎不發(fā)生變化。尺寸類發(fā)生變化的原因如下:
- 視圖控制器創(chuàng)建的水平或垂直方向的尺寸類改變,通過(guò)因?yàn)樵O(shè)備旋轉(zhuǎn)。
- 容器視圖控制器的水平或垂直方向的尺寸類改變。
- 當(dāng)前視圖控制器的容器顯式改變當(dāng)前視圖控制器的水平或垂直方向的尺寸類。
視圖控制器層級(jí)結(jié)構(gòu)中尺寸類變化會(huì)向下傳遞到所有子視圖控制器。作為層級(jí)結(jié)構(gòu)中跟對(duì)象的窗口對(duì)象,為其根視圖控制器提供了基準(zhǔn)的尺寸類特征。設(shè)備方向在豎屏和橫屏之間變化時(shí),窗口更新自身的尺寸類信息,并把該信息沿著視圖控制器層級(jí)結(jié)構(gòu)向下傳遞。容器視圖控制器可以傳遞未經(jīng)修改的變化給子視圖控制器,或者可以覆寫每個(gè)子視圖控制器的特征。
在iOS 8及以后的版本中,窗口原點(diǎn)總在左上角,設(shè)備橫豎屏切換時(shí),窗口的bounds會(huì)變化。窗口尺寸的變化與相應(yīng)的特征變化一起沿著視圖控制器層級(jí)結(jié)構(gòu)向下傳遞。UIKit調(diào)用層級(jí)結(jié)構(gòu)中每一個(gè)視圖控制器的以下方法來(lái)報(bào)告這些改變:
- willTransitionToTraitCollection:withTransitionCoordinator:方法告訴每個(gè)相關(guān)的視圖控制器,它的特征即將改變。
- viewWillTransitionToSize:withTransitionCoordinator:方法告訴每個(gè)相關(guān)的視圖控制器,它的尺寸即將改變。
- traitCollectionDidChange:告訴每個(gè)相關(guān)的視圖控制器,它的特征已經(jīng)發(fā)生變化。
遍歷視圖控制器層級(jí)結(jié)構(gòu)時(shí),UIKit只在有變化時(shí)才報(bào)告視圖控制器。如果容器視圖控制器覆寫了子視圖控制器的尺寸類,容器的尺寸類變化時(shí),這些子視圖控制器不會(huì)收到通知。同樣的,如果視圖控制器的視圖有固定的寬度和高度,它不會(huì)收到尺寸變化通知。
圖12-2展示了選擇iPhone 6時(shí),視圖控制器的特征和視圖尺寸如何更新。從豎屏旋轉(zhuǎn)到橫屏?xí)r,屏幕的垂直尺寸類從常規(guī)變?yōu)榫o湊。接著,尺寸類變化和相應(yīng)的視圖尺寸變化沿著視圖控制器層級(jí)結(jié)構(gòu)向下傳遞。動(dòng)畫(huà)改變視圖為新尺寸后,UIKit在調(diào)用視圖控制器的traitCollectionDidChange:方法之前,應(yīng)用尺寸類和視圖尺寸變化。
圖12-2 更新視圖控制器的特征是視圖尺寸
1.3 不同設(shè)備的默認(rèn)尺寸類
每個(gè)iOS設(shè)備都有默認(rèn)的尺寸類集,設(shè)計(jì)界面時(shí),可以作為參考。表格12-2列出了設(shè)備在豎屏和橫屏?xí)r的尺寸類。表格中沒(méi)有列出的設(shè)備與相同屏幕尺寸設(shè)備的尺寸類相同。
表格 12-2 不同屏幕尺寸設(shè)備的尺寸類
| 設(shè)備 | 豎屏 | 橫屏 |
|---|---|---|
| iPad(所有) iPad Mini |
Vertical size class: Regular Horizontal size class: Regular |
Vertical size class: Regular Horizontal size class: Regular |
| iPhone 6 Plus | Vertical size class: Regular Horizontal size class: Compact |
Vertical size class: Compact Horizontal size class: Regular |
| iPhone 6 | Vertical size class: Regular Horizontal size class: Compact |
Vertical size class: Compact Horizontal size class: Compact |
| iPhone 5s iPhone 5c iPhone 5 |
Vertical size class: Regular Horizontal size class: Compact |
Vertical size class: Compact Horizontal size class: Compact |
| iPhone 4s | Vertical size class: Regular Horizontal size class: Compact |
Vertical size class: Compact Horizontal size class: Compact |
重要:永遠(yuǎn)不要假設(shè)應(yīng)用程序會(huì)在特定的size class設(shè)備上應(yīng)用。決定如何配置對(duì)象時(shí),總是在該對(duì)象的特征集合中檢查size class。
2 創(chuàng)建自適應(yīng)界面
自適應(yīng)界面應(yīng)該同時(shí)響應(yīng)特征和尺寸變化。在視圖控制器級(jí)別,使用特征為顯示的內(nèi)容和內(nèi)容的布局做粗粒度級(jí)別的決定。例如,當(dāng)尺寸類變化是,可以選擇改變視圖屬性,顯示或隱藏視圖,或者顯示一組完全不同的視圖。做出這些大的決策后,使用尺寸變化來(lái)調(diào)整內(nèi)容。
2.1 自適應(yīng)特征變化
特征為不同環(huán)境配置應(yīng)用程序提供了一種方式,并可以使用它們粗粒度的調(diào)整界面。大部分特征變化可以直接在故事版文件中完成,有些需要額外的代碼。
2.1.1 配置故事版處理不同的尺寸類
界面生成器讓界面很容易適應(yīng)不同的尺寸類。故事版編輯器支持在不同尺寸類配置中顯示界面,在特定配置中移除視圖,以及指定不同的布局約束。還可以圖片資源,為不同尺寸類提供不同的圖片。使用這些工具意味著不需要在運(yùn)行時(shí)通過(guò)代碼做出相同的改變。相反,當(dāng)前尺寸類變化時(shí),UIKit自動(dòng)更新界面。
圖13-1展示了界面生成器中用來(lái)配置界面的工具。尺寸類查看控件(viewing control)改變界面的外觀。使用該控件查看界面在給定的尺寸類中的外觀。對(duì)于單個(gè)視圖,使用安裝控制(installation control)配置該視圖在給定的尺寸類配置中是否應(yīng)該顯示。使用復(fù)選框左邊的加號(hào)按鈕添加新的配置。
圖13-1 為不同尺寸類自定義界面
提示:未安裝的視圖仍然在視圖層級(jí)結(jié)構(gòu)中,可以正常操作,但不在屏幕上顯示。
圖片資產(chǎn)(image asset)是存儲(chǔ)應(yīng)用程序圖片資源的推薦方式。每個(gè)圖片資產(chǎn)包括同一張圖片的多個(gè)版本,每個(gè)版本用于特定配置。除了為標(biāo)準(zhǔn)和視網(wǎng)膜顯示屏指定不同的圖片,還可以為不同的水平和垂直尺寸類指定不同圖片。配置圖片資產(chǎn)后,UIImageView對(duì)象根據(jù)當(dāng)前尺寸類和分辨率自動(dòng)選擇圖片。
圖13-2展示了圖片資產(chǎn)的屬性。改變寬度和高度屬性會(huì)在目錄上為更多圖片添加插槽。為每種尺寸類組合填滿這些插槽。
圖13-2 為不同尺寸類配置圖片資產(chǎn)
2.1.2 改變子視圖控制器的特征
子視圖控制器默認(rèn)繼承父視圖控制器的特征。每個(gè)子視圖控制器與父視圖控制器有相同的特征可能沒(méi)有意義,比如尺寸類。例如,常規(guī)環(huán)境中的視圖控制器可能指定一個(gè)或多個(gè)子視圖控制器為緊湊尺寸類來(lái)減少該子視圖控制器的空間。實(shí)現(xiàn)容器視圖控制器時(shí),通過(guò)容器視圖控制器的setOverrideTraitCollection:forChildViewController:方法修改子視圖控制器的特征。
列表13-1展示了如何創(chuàng)建新的特征集,并關(guān)聯(lián)到子視圖控制器。只需要在父視圖控制器中執(zhí)行一次這段代碼。覆蓋的特征屬于子視圖控制器,直到再次改變它們,或者從視圖控制器層級(jí)結(jié)構(gòu)中移除子視圖控制器。
列表13-1 改變子視圖控制器的特征
UITraitCollection* horizTrait = [UITraitCollection
traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
UITraitCollection* vertTrait = [UITraitCollection
traitCollectionWithVerticalSizeClass:UIUserInterfaceSizeClassCompact];
UITraitCollection* childTraits = [UITraitCollection
traitCollectionWithTraitsFromCollections:@[horizTrait, vertTrait]];
[self setOverrideTraitCollection:childTraits forChildViewController:self.childViewControllers[0]];
父視圖控制器的特征改變時(shí),子視圖控制器繼承所有父視圖控制器沒(méi)有顯式覆蓋的特征。例如,當(dāng)父視圖控制器的水平尺寸類從常規(guī)變?yōu)榫o湊時(shí),上面例子中的子視圖控制器仍然保持水平方向的常規(guī)尺寸類。然而,如果displayScale特征改變,子視圖控制器繼承新的值。
2.1.3 使Presented視圖控制器適應(yīng)新風(fēng)格
Presented視圖控制器在水平方向?yàn)槌R?guī)和緊湊環(huán)境中自動(dòng)適應(yīng)。從水平常規(guī)過(guò)渡到水平緊湊環(huán)境時(shí),UIKit默認(rèn)改變內(nèi)置彈出風(fēng)格為UIModalPresentationFullScreen。對(duì)于自定義彈出樣式,彈出控制器可以決定適應(yīng)行為,并據(jù)此調(diào)整彈出。
對(duì)于某些應(yīng)用程序,適應(yīng)為全屏風(fēng)格可能會(huì)存在問(wèn)題。例如,通常通過(guò)點(diǎn)擊彈出框bounds外部來(lái)關(guān)閉它,但是在緊湊環(huán)境中沒(méi)辦法做到,因?yàn)閺棾隹驎?huì)覆蓋整個(gè)屏幕,如圖13-3所示。當(dāng)默認(rèn)適應(yīng)風(fēng)格不合適時(shí),可以告訴UIKit使用不同的風(fēng)格,或者彈出一個(gè)更適合全屏風(fēng)格的,完全不同的視圖控制器。
圖13-3 常規(guī)和緊湊環(huán)境中的彈出框
要改變彈出風(fēng)格的默認(rèn)行為,需要指定一個(gè)代理給關(guān)聯(lián)的彈出控制器。使用presented視圖控制器的presentationController屬性訪問(wèn)彈出控制器。彈出控制器做任何適應(yīng)相關(guān)的改變前,向代理對(duì)象咨詢。代理可以返回不同的彈出風(fēng)格,而不會(huì)默認(rèn)的,并且它可以給彈出控制器提供一個(gè)要顯示的代替視圖控制器。
使用代理的adaptivePresentationStyleForPresentationController:方法指定一個(gè)不同與默認(rèn)的彈出風(fēng)格。當(dāng)過(guò)渡到緊湊環(huán)境中時(shí),僅支持兩種全屏風(fēng)格或UIModalPresentationNone。返回UIModalPresentationNone告訴彈出控制器,忽略緊湊環(huán)境,繼續(xù)使用上一個(gè)彈出風(fēng)格。在彈出框的情況下,忽略改變會(huì)在所有設(shè)備上提供類似iPad上的彈出框。圖13-4并排展示了默認(rèn)的全屏適應(yīng)和沒(méi)有適應(yīng)。
圖13-4 改變presented視圖控制器的適應(yīng)行為
要一起替換視圖控制器,實(shí)現(xiàn)代理的presentationController:viewControllerForAdaptivePresentationStyle:方法。當(dāng)適應(yīng)到緊湊環(huán)境時(shí),可以使用該方法在視圖層級(jí)結(jié)構(gòu)中插入導(dǎo)航控制器,或者加載為小空間設(shè)計(jì)的視圖控制器。
2.1.4 實(shí)現(xiàn)自適應(yīng)彈出框(Popover)的技巧
從水平方向常規(guī)變化到水平方向緊湊環(huán)境時(shí),彈出框需要額外的修改。水平方向緊湊環(huán)境默認(rèn)將彈出框改為全屏彈出。因?yàn)橥ǔMㄟ^(guò)點(diǎn)擊彈出框bounds外部來(lái)關(guān)閉,所以變?yōu)槿翉棾鰰r(shí)沒(méi)法關(guān)閉彈出框。可以通過(guò)下面其中一種方式關(guān)閉彈出框:
- 把彈出框的視圖控制器壓入現(xiàn)有的導(dǎo)航棧中。如果父導(dǎo)航控制器可用,關(guān)閉彈出框,并把它的視圖控制器壓入導(dǎo)航棧中。
- 全屏彈出時(shí),添加控件來(lái)關(guān)閉彈出框。可用添加控件到彈出框的視圖控制器,更好的選擇是使用presentationController:viewControllerForAdaptivePresentationStyle:方法為導(dǎo)航控制器置換出彈出框。使用導(dǎo)航控制器有一個(gè)模態(tài)界面和控件,可用添加完成按鈕或其它控件來(lái)關(guān)閉內(nèi)容。
- 使用彈出控制器代理消除任何自適應(yīng)變化。獲得彈出框彈出控制器,并分配一個(gè)代理給它,該代理實(shí)現(xiàn)adaptivePresentationStyleForPresentationController:方法。該方法返回UIModalPresentationNone,讓彈出框繼續(xù)以彈出框顯示。更多信息請(qǐng)參考“使Presented視圖控制器適應(yīng)新風(fēng)格”。
2.2 響應(yīng)尺寸變化
尺寸變化的原因有很多,包括以下幾點(diǎn):
- 底層窗口的尺寸改變,通常因?yàn)榉较蜃兓?/li>
- 父視圖控制器調(diào)整其中一個(gè)子視圖控制器的尺寸。
- 彈出控制器改變它的presented視圖控制器的尺寸。
發(fā)生尺寸改變時(shí),UIKit通過(guò)正常的布局過(guò)程,自動(dòng)更新可見(jiàn)的視圖控制器的尺寸和位置。如果使用自動(dòng)布局約束指定視圖的尺寸和位置,應(yīng)用程序自動(dòng)適應(yīng)所有尺寸變化,并可以在不同屏幕尺寸上運(yùn)行。
如果自動(dòng)布局約束不足以達(dá)到想要的效果,可以使用viewWillTransitionToSize:withTransitionCoordinator:方法改變布局。也可以使用該方法創(chuàng)建于尺寸改變動(dòng)畫(huà)同時(shí)發(fā)生的額外動(dòng)畫(huà)。例如,界面旋轉(zhuǎn)過(guò)程中,可以使用過(guò)渡協(xié)調(diào)器的targetTransform屬性為界面的一部分創(chuàng)建反向旋轉(zhuǎn)的矩陣。