一、基本概念 html
在 iPad 和 iPhone 5 出現以前,iOS 設備就只有一種尺寸。咱們在作屏幕適配時須要考慮的僅僅有設備方向而已。而不少應用並不支持轉向,這樣的話就徹底沒有屏幕適配的工做了。隨着 iPad 和 iPhone 5,以及接下來的 iPhone 6 的推出,屏幕尺寸也變成了須要考慮的對象。在 iOS 7 以前,爲一個應用,特別是 universal 的應用製做 UI 時,咱們總會首先想咱們的目標設備的長寬各是多少,方向變換之後佈局又應該怎麼改變,而後進行佈局。iOS 6 引入了 Auto Layout 來幫助開發者使用約束進行佈局,這使得在某些狀況下咱們再也不須要考慮尺寸,而能夠專一於使用約束來規定位置。 ios
既然咱們有了 Auto Layout,那麼其實經過約束來指定視圖的位置和尺寸是沒有什麼問題的了,從這個方面來講,屏幕的具體的尺寸和方向已經不那麼重要了。可是實戰中這還不夠,Auto Layout 正如其名,只是一個根據約束來進行佈局的方案,而在對應不一樣設備的具體狀況下的體驗上還有欠缺。一個最明顯的問題是它不能根據設備類型來肯定不一樣的交互體驗。不少時候你仍是須要判斷設備究竟是 iPhone 仍是 iPad,以及如今的設備方向到底是豎直仍是水平來作出判斷。這樣的話咱們仍是難以完全擺脫對於設備的判斷和依賴,而以後若是有新的尺寸和設備出現的話,這種依賴關係顯然顯得十分脆弱的(想一想要是有 iWatch 的話..)。 web
因此在 iOS 8 裏,Apple 從最初的設計哲學上將原來的方式推翻了,並引入了一整套新的理念,來適應設備不斷的發展。這就是 Size Classes。 app
再也不根據設備屏幕的具體尺寸來進行區分,而是經過它們的感官表現,將其分爲普通 (Regular) 和緊密 (Compact) 兩個種類 (class)。開發者即可以無視具體的尺寸,而是對這這兩類和它們的組合進行適配。這樣不論在設計時仍是代碼上,咱們均可以再也不受限於具體的尺寸,而是變成遵循尺寸的視覺感官來進行適配。 iphone
Size Classes有三個值:Regular,Compact 和Any。Any是什麼意思呢?若是weight設爲Any,height設置爲Regular,那麼在該狀態下的界面元素在只要height爲Regular,不管weight是Regular仍是Compact的狀態中都會存在。這種關係應該叫作繼承關係,具體的四種界面描述與可繼承的界面描述以下: ide
w:Compact h:Compact 繼承 (w:Any h:Compact , w:Compact h:Any , w:Any h:Any) 佈局
w:Regular h:Compact 繼承 (w:Any h:Compact , w:Regular h:Any , w:Any h:Any) 字體
w:Compact h:Regular 繼承 (w:Any h:Regular , w:Compact h:Any , w:Any h:Any) 動畫
w:Regular h:Regular 繼承 (w:Any h:Regular , w:Regular h:Any , w:Any h:Any) ui
這麼多設備(iPhone4S,iPhone5/5s,iPhone6,iPhone6 Plus,iPad,Apple Watch)的尺寸,就經過Size Classes簡單的表達出來了:
PS:附上圖形:
二、UITraitCollection 和 UITraitEnvironment (Size Classes手寫代碼)
爲了表徵 Size Classes,Apple 在 iOS 8 中引入了一個新的類,UITraitCollection。這個類封裝了像水平和豎直方向的 Size Class 等信息。iOS 8 的 UIKit 中大多數 UI 的基礎類 (包括UIScreen,UIWindow,UIViewController 和 UIView) 都實現了 UITraitEnvironment 這個接口,經過其中的 traitCollection 這個屬性,咱們能夠拿到對應的 UITraitCollection 對象,從而得知當前的 Size Class,並進一步肯定界面的佈局。
和 UIKit 中的響應者鏈正好相反,traitCollection 將會在 view hierarchy 中自上而下地進行傳遞。對於沒有指定 traitCollection 的 UI 部件,將使用其父節點的 traitCollection。這在佈局包含 childViewController 的界面的時候會至關有用。在 UITraitEnvironment 這個接口中另外一個很是有用的是 -traitCollectionDidChange:。在 traitCollection 發生變化時,這個方法將被調用。在實際操做時,咱們每每會在 ViewController 中重寫 -traitCollectionDidChange: 或者 -willTransitionToTraitCollection:withTransitionCoordinator: 方法 (對於 ViewController 來講的話,後者也許是更好的選擇,由於提供了轉場上下文方便進行動畫;可是對於普通的 View 來講就只有前面一個方法了),而後在其中對當前的 traitCollection 進行判斷,並進行從新佈局以及動畫。代碼看起來大概會是這個樣子:
在兩個 To Do 中,咱們應該刪除或者添加或者更改不一樣條件下的 Auto Layout 約束 (固然,你也能夠幹其餘任何你想作的事情),而後調用 -setNeedsLayout 來在上下文中觸發轉移動畫。若是你堅持用代碼來處理的話,可能須要面臨對於不一樣 Size Classes 來作移除舊的約束和添加新的約束這樣的事情,能夠說是很麻煩 (至少我以爲是麻煩的要死)。可是若是咱們使用 IB 的話,這些事情和代碼均可以省掉,咱們能夠很是方便地在 IB 中指定各類 Size Classes 的約束 (稍後會介紹如何使用 IB 來對應 Size Classes)。另外使用 IB 不只能夠節約成百上千行的佈局代碼,更能夠重新的 Xcode 和 IB 中獲得不少設計時就能夠實時監視,查看而且調試的特性。能夠說手寫 UI 和使用 IB 設計的時間消耗和成本差距被進一步拉大,而且出現了不少手寫 UI 沒法實現,可是 IB 能夠不假思索地完成的任務。從這個意義上來講,新的 IB 和 Size Classes 系統能夠說無情地給手寫代碼判了個死緩。
另外,新的 API 和體系的引入也同時給不少咱們熟悉的 UIViewController 的有關旋轉的老朋友判了死刑,好比下面這些 API 都棄用了:
@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation - willRotateToInterfaceOrientation:duration: - willAnimateRotationToInterfaceOrientation:duration: - didRotateFromInterfaceOrientation: - shouldAutomaticallyForwardRotationMethods
如今所有統一到了 viewWillTransitionToSize:withTransitionCoordinator:,旋轉的概念再也不被提倡使用。其實仔細想一想,所謂旋轉,不過就是一種 Size 的改變而已,咱們都被 Apple 騙了好多年,不是麼?
三、Interface Builder 中使用 Size Classes
建立一個新的通用項目。若是你想要早在一個已經建立了的Xcode6項目,你須要激活size classes選項。你能夠在Interface Builder中的屬性面板 勾選autolayout 的選項的下面找到它。
首先,讓咱們在Xcode中看一下size class的網格。這是一個你能夠在不一樣的佈局排列間切換的區域。當你查看storyboard的時候,看到視圖的底部,而且點擊‘wAny hAny’字樣的標籤。你將會看到一些相似網格的畫面。
默認的,咱們以一個基礎的設置開始,也就是any width和any height。不少事情都將在這裏安置和改變,包括了iphone和ipad的全部方向的默認佈局。蘋果建議把大多數的設置都在這個界面中進行設置。這個是由於減小工做量而顯得特別的簡單。讓咱們佈局一個超級寬的按鈕在畫面的中間。給它一個綠色的背景,從而讓咱們看到它真實的尺寸,給它一個約束來讓他居中。
而且給它一個誇張的固定寬度600。
好了,如今在ipad和iphone的模擬器都運行一下,你將會看到都是居中,但對於iphone的兩個方向都太寬了,(這裏你設置了頁面中button的寬度 但並無立刻更新是由於 你在作添加約束的時候沒有更新圖形,致使了以下圖的狀況,storyboard裏面沒有更新,而在模擬器運行時候更新了,左邊大綱欄目裏面也有警告說明,能夠直接點擊警告裏面的黃色三角來更新畫面其實就是 Updata Frame)
讓咱們使用size classes來修正吧。回到剛纔那個第一張圖的網格選擇iphone的縱向(portrait)設置,就是緊湊的寬度+ 常規的高度。網格中的紅色矩形。
你將會注意到你在網格中選中以後底部的bar改變爲藍色。那是在警告你:「Hey ,你並非在一個基礎的設置,有些改變將會只在你運行的時候顯示。因此這個bar如今是藍色的!」 我所說的一些改變是由於有四項你能改變的size classes:1約束常數,2字體,3約束的開/關,4子視圖的開/關。
前兩個是不言而喻的,可是讓我來告訴你如何讓後二者工做。在當前的size class (compact width 和 regular height)情況下讓咱們試着把一個約束關閉。在文檔的提綱欄裏,點擊設置在咱們的button的Centre X 校準約束:
如今看一下咱們的屬性檢查欄,在底部咱們能夠看到帶標記的一個單詞「Installed」,而且左側有額外的加號按鈕。點擊額外的加號而且點選'Compact Width| Regular Height'(當前的就是)。
如今你將會看到2個標記物,把剛剛添加的哪個取消勾選(wC hR)
如今咱們的約束再也不安置而且作任何事情來配置size classes。就像你看到的,Xcode正在控訴咱們的約束太混亂了(左邊的大綱會有錯誤提示表示你缺乏了約束-譯者),若是你這時候運行app在iphone的模擬器上的話,按鈕不在X方向居中了。可是在ipad的上面仍是居中的,由於約束仍然安置在基本的設置裏面。這個約束將會一直配置着除非咱們把它取消勾選。你甚至可以旋轉你的iphone模擬器,而且發現button將會神奇的回到居中,由於iphone的橫向是不一樣的size class配置,好了,讓咱們把勾選回來,讓button回到居中。
如今讓咱們改變咱們設置在button寬度的約束,選擇button,而且來到Size的屬性檢查欄,下拉到底部,咱們能夠看到全部的約束。點擊Width本來是600的 使用Edit設置爲100:
在iPhone的模擬器上運行,你將會看到button已經具有了正確的寬度。運行在ipad的模擬器的時候卻展現了600的寬度,由於咱們沒有改變基本設置裏面的寬度。可是,在iphone的橫向landscape仍然看着不怎麼樣,由於iphone的橫向設置來自基本的Any Any 的設置。讓咱們修正一下。在網格里面咱們選擇compact Width和Compact Height。也就是第一張圖的藍色網格。
如今咱們在這個設置下改變width 的約束,就像咱們爲了compact x regular改變的同樣。給予一個400的寬度。運行一下iphone的模擬器,而且旋轉到橫向,按鈕有了400的寬度,看上去很棒。達到了咱們的預想。有一點很好就是你能看到一個全部的約束的列表,這些都是不一樣的設置的。僅僅選擇你想要在文檔大綱裏面看到的約束,而後來到屬性檢查欄,他們整齊的排列在初始的常數下面。它標註了每個基於它所應用的設置。
即便咱們決定咱們想要只在iphone橫向landscape模式下button消失,使用size classes 咱們只要反向安置views就像咱們反向安置一個約束。選擇咱們的UIbutton,滾動到屬性檢查器的底部。經過點擊 加號按鈕 給咱們當前的設置添加一個新的安置選項,而後取消勾選它。
就像你看到的,那個view 立馬消失了,由於咱們在設置裏面反向安置了它,咱們立馬就能看到。運行app,你能看到它在縱向的portrait iphone上消失了,可是當你旋轉到橫向的landscape的時候又回來了。固然它也一直安置在ipad上面由於ipad仍然使用的是基本的設置。
四、Size Classes 和 Image Asset 及 UIAppearence
Image Asset 裏也加入了對 Size Classes 的支持,也就是說,咱們能夠對不一樣的 Size Class 指定不一樣的圖片了。在 Image Asset 的編輯面板中選擇某張圖片,Inspector 裏如今多了一個 Width 和 Height 的組合,添加咱們須要對應的 Size Class, 而後把合適的圖拖上去,這樣在運行時 SDK 就將從中挑選對應的 Size 的圖進行替換了。不只如此,在 IB 中咱們也能夠選擇對應的 size 來直接在編輯時查看變化。
實際作起來實在是太簡單了..但拿個 demo 說明一下吧,好比下面這個實現了豎直方向 Compact 的時候將笑臉換成哭臉 -- 固然了,一行代碼都不須要。
另外,在 iOS 7 中 UIImage 添加了一個 renderingMode 屬性。咱們可使用imageWithRenderingMode: 並傳入一個合適的 UIImageRenderingMode 來指定這個 image 要不要以Template 的方式進行渲染。在新的 Xcode 中,咱們能夠直接在 Image Asset 裏的 Render As 選項來指定是否是須要做爲 template 使用。而相應的,在 UIApperance 中,Apple 也爲咱們對於 Size Classes 添加了相應的方法。使用 +appearanceForTraitCollection: 方法,咱們就能夠針對不一樣 trait 下的應用的 apperance 進行很簡單的設定。好比在上面的例子中,咱們想讓笑臉是綠色,而哭臉是紅色的話,不要太簡單。首先在 Image Asset 裏的渲染選項設置爲 Template Image,而後直接在 AppDelegate 里加上這樣兩行:
UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Compact)).tintColor = UIColor.redColor() UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Regular)).tintColor = UIColor.greenColor()
完成,只不過拖拖鼠標,兩行簡單的代碼,隨後還能隨喜換色,果真是大快全部人心的大好事。