Size Classes with Xcode 6

一、基本概念app

在iPad和iPhone 5出現以前,iOS設備就只有一種尺寸。咱們在作屏幕適配時須要考慮的僅僅有設備方向而已。而不少應用並不支持轉向,這樣的話就徹底沒有屏幕適配的工做了。隨着iPad和iPhone 5,以及接下來的iPhone 6的推出,屏幕尺寸也變成了須要考慮的對象。在iOS7以前,爲一個應用,特別是universal的應用製做UI時,咱們總會首先想咱們的目標設備的長寬各是多少,方向變換之後佈局又應該怎麼改變,而後進行佈局。iOS6引入了AutoLayout來幫助開發者使用約束進行佈局,這使得在某些狀況下咱們再也不須要考慮尺寸,而能夠專一於使用約束來規定位置。iphone

既然咱們有了AutoLayout,那麼其實經過約束來指定視圖的位置和尺寸是沒有什麼問題的了,從這個方面來講,屏幕的具體的尺寸和方向已經不那麼重要了。可是實戰中這還不夠,AutoLayout正如其名,只是一個根據約束來進行佈局的方案,而在對應不一樣設備的具體狀況下的體驗上還有欠缺。一個最明顯的問題是它不能根據設備類型來肯定不一樣的交互體驗。不少時候你仍是須要判斷設備究竟是iPhone仍是iPad,以及如今的設備方向到底是豎直仍是水平來作出判斷。這樣的話咱們仍是難以完全擺脫對於設備的判斷和依賴,而以後若是有新的尺寸和設備出現的話,這種依賴關係顯然顯得十分脆弱的(想一想要是有iWatch的話..)。ide

因此在iOS8裏,Apple從最初的設計哲學上將原來的方式推翻了,並引入了一整套新的理念,來適應設備不斷的發展。這就是SizeClasses。佈局

再也不根據設備屏幕的具體尺寸來進行區分,而是經過它們的感官表現,將其分爲普通(Regular)和緊密(Compact)兩個種類(class)。開發者即可以無視具體的尺寸,而是對這這兩類和它們的組合進行適配。這樣不論在設計時仍是代碼上,咱們均可以再也不受限於具體的尺寸,而是變成遵循尺寸的視覺感官來進行適配。字體

SizeClasses有三個值:Regular,Compact和Any。Any是什麼意思呢?若是weight設爲Any,height設置爲Regular,那麼在該狀態下的界面元素在只要height爲Regular,不管weight是Regular仍是Compact的狀態中都會存在。這種關係應該叫作繼承關係,具體的四種界面描述與可繼承的界面描述以下:動畫

1
2
3
4
w:Compacth:Compact繼承(w:Anyh:Compact,w:Compacth:Any,w:Anyh:Any)
w:Regularh:Compact繼承(w:Anyh:Compact,w:Regularh:Any,w:Anyh:Any)
w:Compacth:Regular繼承(w:Anyh:Regular,w:Compacth:Any,w:Anyh:Any)
w:Regularh:Regular繼承(w:Anyh:Regular,w:Regularh:Any,w:Anyh:Any)

這麼多設備(iPhone 4S,iPhone 5/5s,iPhone 6,iPhone 6Plus,iPad,AppleWatch)的尺寸,就經過SizeClasses簡單的表達出來了:ui

iPhone4S,iPhone 5/5s,iPhone 6atom

豎屏:(w:Compacth:Regular)spa

橫屏:(w:Compacth:Compact)設計

iPhone6Plus

豎屏:(w:Compacth:Regular)

橫屏:(w:Regularh:Compact)

iPad

豎屏:(w:Regularh:Regular)

橫屏:(w:Regularh:Regular)

AppleWatch(猜想)

豎屏:(w:Compacth:Compact)

橫屏:(w:Compacth:Compact)

 

 

PS:附上圖形:

二、UITraitCollection和UITraitEnvironment(Size Classes手寫代碼)

爲了表徵SizeClasses,Apple在iOS 8中引入了一個新的類,UITraitCollection。這個類封裝了像水平和豎直方向的SizeClass等信息。iOS 8的UIKit中大多數UI的基礎類(包括UIScreen,UIWindow,UIViewController和UIView)都實現了UITraitEnvironment這個接口,經過其中的traitCollection這個屬性,咱們能夠拿到對應的UITraitCollection對象,從而得知當前的SizeClass,並進一步肯定界面的佈局。

和UIKit中的響應者鏈正好相反,traitCollection將會在viewhierarchy中自上而下地進行傳遞。對於沒有指定traitCollection的UI部件,將使用其父節點的traitCollection。這在佈局包含childViewController的界面的時候會至關有用。在UITraitEnvironment這個接口中另外一個很是有用的是-traitCollectionDidChange:。在traitCollection發生變化時,這個方法將被調用。在實際操做時,咱們每每會在ViewController中重寫-traitCollectionDidChange:或者-willTransitionToTraitCollection:withTransitionCoordinator:方法(對於ViewController來講的話,後者也許是更好的選擇,由於提供了轉場上下文方便進行動畫;可是對於普通的View來講就只有前面一個方法了),而後在其中對當前的traitCollection進行判斷,並進行從新佈局以及動畫。代碼看起來大概會是這個樣子:

    override func willTransitionToTraitCollection(newCollection: UITraitCollection, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator){
        super.willTransitionToTraitCollection(newCollection, withTransitionCoordinator: coordinator)
     
        coordinator.animateAlongsideTransition({ (context: UIViewControllerTransitionCoordinatorContext!) -> Void in
            if (newCollection.verticalSizeClass == UIUserInterfaceSizeClass.Compact) {
                //To Do: modify something for compact vertical size
            } else {
                //To Do: modify something for other vertical size
            }
            self.view.setNeedsLayout()
        }, completion: nil)
    }

 

在兩個To Do中,咱們應該刪除或者添加或者更改不一樣條件下的AutoLayout約束(固然,你也能夠幹其餘任何你想作的事情),而後調用-setNeedsLayout來在上下文中觸發轉移動畫。若是你堅持用代碼來處理的話,可能須要面臨對於不一樣SizeClasses來作移除舊的約束和添加新的約束這樣的事情,能夠說是很麻煩(至少我以爲是麻煩的要死)。可是若是咱們使用IB的話,這些事情和代碼均可以省掉,咱們能夠很是方便地在IB中指定各類SizeClasses的約束(稍後會介紹如何使用IB來對應SizeClasses)。另外使用IB不只能夠節約成百上千行的佈局代碼,更能夠重新的Xcode和IB中獲得不少設計時就能夠實時監視,查看而且調試的特性。能夠說手寫UI和使用IB設計的時間消耗和成本差距被進一步拉大,而且出現了不少手寫UI沒法實現,可是IB能夠不假思索地完成的任務。從這個意義上來講,新的IB和SizeClasses系統能夠說無情地給手寫代碼判了個死緩。

 

另外,新的API和體系的引入也同時給不少咱們熟悉的UIViewController的有關旋轉的老朋友判了死刑,好比下面這些API都棄用了:

@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation
 
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
- shouldAutomaticallyForwardRotationMethods

如今所有統一到了viewWillTransitionToSize:withTransitionCoordinator:,旋轉的概念再也不被提倡使用。其實仔細想一想,所謂旋轉,不過就是一種Size的改變而已,咱們都被Apple騙了好多年,不是麼?

 

三、InterfaceBuilder中使用SizeClasses

建立一個新的通用項目。若是你想要早在一個已經建立了的Xcode6項目,你須要激活sizeclasses選項。你能夠在InterfaceBuilder中的屬性面板勾選autolayout的選項的下面找到它。

 

首先,讓咱們在Xcode中看一下sizeclass的網格。這是一個你能夠在不一樣的佈局排列間切換的區域。當你查看storyboard的時候,看到視圖的底部,而且點擊‘wAnyhAny’字樣的標籤。你將會看到一些相似網格的畫面。

 

 

默認的,咱們以一個基礎的設置開始,也就是anywidth和anyheight。不少事情都將在這裏安置和改變,包括了iphone和ipad的全部方向的默認佈局。蘋果建議把大多數的設置都在這個界面中進行設置。這個是由於減小工做量而顯得特別的簡單。讓咱們佈局一個超級寬的按鈕在畫面的中間。給它一個綠色的背景,從而讓咱們看到它真實的尺寸,給它一個約束來讓他居中。

而且給它一個誇張的固定寬度600。

好了,如今在ipad和iphone的模擬器都運行一下,你將會看到都是居中,但對於iphone的兩個方向都太寬了,(這裏你設置了頁面中button的寬度但並無立刻更新是由於你在作添加約束的時候沒有更新圖形,致使了以下圖的狀況,storyboard裏面沒有更新,而在模擬器運行時候更新了,左邊大綱欄目裏面也有警告說明,能夠直接點擊警告裏面的黃色三角來更新畫面其實就是UpdataFrame)

 

 

讓咱們使用sizeclasses來修正吧。回到剛纔那個第一張圖的網格選擇iphone的縱向(portrait)設置,就是緊湊的寬度+常規的高度。網格中的紅色矩形.

你將會注意到你在網格中選中以後底部的bar改變爲藍色。那是在警告你:「Hey,你並非在一個基礎的設置,有些改變將會只在你運行的時候顯示。因此這個bar如今是藍色的!」我所說的一些改變是由於有四項你能改變的sizeclasses:1約束常數,2字體,3約束的開/關,4子視圖的開/關。

前兩個是不言而喻的,可是讓我來告訴你如何讓後二者工做。在當前的sizeclass(compactwidth和regularheight)情況下讓咱們試着把一個約束關閉。在文檔的提綱欄裏,點擊設置在咱們的button的CentreX校準約束:

在看一下咱們的屬性檢查欄,在底部咱們能夠看到帶標記的一個單詞「Installed」,而且左側有額外的加號按鈕。點擊額外的加號而且點選'CompactWidth|RegularHeight'(當前的就是)。

如今你將會看到2個標記物,把剛剛添加的哪個取消勾選(wChR)

 

 

如今咱們的約束再也不安置而且作任何事情來配置sizeclasses。就像你看到的,Xcode正在控訴咱們的約束太混亂了(左邊的大綱會有錯誤提示表示你缺乏了約束-譯者),若是你這時候運行app在iphone的模擬器上的話,按鈕不在X方向居中了。可是在ipad的上面仍是居中的,由於約束仍然安置在基本的設置裏面。這個約束將會一直配置着除非咱們把它取消勾選。你甚至可以旋轉你的iphone模擬器,而且發現button將會神奇的回到居中,由於iphone的橫向是不一樣的sizeclass配置,好了,讓咱們把勾選回來,讓button回到居中。

如今讓咱們改變咱們設置在button寬度的約束,選擇button,而且來到Size的屬性檢查欄,下拉到底部,咱們能夠看到全部的約束。點擊Width本來是600的使用Edit設置爲100:

 

在iPhone的模擬器上運行,你將會看到button已經具有了正確的寬度。運行在ipad的模擬器的時候卻展現了600的寬度,由於咱們沒有改變基本設置裏面的寬度。可是,在iphone的橫向landscape仍然看着不怎麼樣,由於iphone的橫向設置來自基本的AnyAny的設置。讓咱們修正一下。在網格里面咱們選擇compactWidth和CompactHeight。也就是第一張圖的藍色網格。

如今咱們在這個設置下改變width的約束,就像咱們爲了compactxregular改變的同樣。給予一個400的寬度。運行一下iphone的模擬器,而且旋轉到橫向,按鈕有了400的寬度,看上去很棒。達到了咱們的預想。有一點很好就是你能看到一個全部的約束的列表,這些都是不一樣的設置的。僅僅選擇你想要在文檔大綱裏面看到的約束,而後來到屬性檢查欄,他們整齊的排列在初始的常數下面。它標註了每個基於它所應用的設置。

即便咱們決定咱們想要只在iphone橫向landscape模式下button消失,使用sizeclasses咱們只要反向安置views就像咱們反向安置一個約束。選擇咱們的UIbutton,滾動到屬性檢查器的底部。經過點擊加號按鈕給咱們當前的設置添加一個新的安置選項,而後取消勾選它。

就像你看到的,那個view立馬消失了,由於咱們在設置裏面反向安置了它,咱們立馬就能看到。運行app,你能看到它在縱向的portraitiphone上消失了,可是當你旋轉到橫向的landscape的時候又回來了。固然它也一直安置在ipad上面由於ipad仍然使用的是基本的設置。

 

四、SizeClasses和ImageAsset及UIAppearence

ImageAsset裏也加入了對SizeClasses的支持,也就是說,咱們能夠對不一樣的SizeClass指定不一樣的圖片了。在ImageAsset的編輯面板中選擇某張圖片,Inspector裏如今多了一個Width和Height的組合,添加咱們須要對應的SizeClass,而後把合適的圖拖上去,這樣在運行時SDK就將從中挑選對應的Size的圖進行替換了。不只如此,在IB中咱們也能夠選擇對應的size來直接在編輯時查看變化。

實際作起來實在是太簡單了..但拿個demo說明一下吧,好比下面這個實現了豎直方向Compact的時候將笑臉換成哭臉--固然了,一行代碼都不須要

 

另外,在iOS7中UIImage添加了一個renderingMode屬性。咱們可使用imageWithRenderingMode:並傳入一個合適的UIImageRenderingMode來指定這個image要不要以Template的方式進行渲染。在新的Xcode中,咱們能夠直接在ImageAsset裏的RenderAs選項來指定是否是須要做爲template使用。而相應的,在UIApperance中,Apple也爲咱們對於SizeClasses添加了相應的方法。使用+appearanceForTraitCollection:方法,咱們就能夠針對不一樣trait下的應用的apperance進行很簡單的設定。好比在上面的例子中,咱們想讓笑臉是綠色,而哭臉是紅色的話,不要太簡單。首先在ImageAsset裏的渲染選項設置爲TemplateImage,而後直接在AppDelegate里加上這樣兩行:

 

UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Compact)).tintColor=UIColor.redColor()
UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Regular)).tintColor=UIColor.greenColor()
相關文章
相關標籤/搜索