一篇文章詳解iOS之AutoResizing、AutoLayout、sizeClass來龍去脈

前言

iPhone自誕生以來,隨著其屏幕尺寸不斷的多樣化,屏幕適配的技術一直在發展更新。目前,iOS系統版本已經更新到9.3,XCode的最新版本已經是7.3,僅iPhone歷史產品的尺寸就已經有4種:3.5英寸、4.0英寸、4.7英寸、5.5英寸。最近,iPhone家族又誕生一款iPhoneSE,鑒于這款iPhoneSE的屏幕尺寸和iPhone5S的尺寸一模一樣——同樣是4.0英寸,廣大iOS開發者可算是松了口氣,不然iOS的屏幕尺寸真的是越來越讓人眼花繚亂。
按照時間順序,屏幕適配是這樣發展的:純代碼計算frame-> autoresizing(早期進行UI布局的技術,僅適用于約束父子控件之間的關系)->AutoLayout(iOS6/2012年、iPhone5被引入,比autoresizing更加高級,旨在替代autoresizing,可以設置任何控件之間的關系)->sizeClass(iOS8出現,用于解決越來越多的屏幕尺寸的適配問題)。
在iPhone3gs時代,手機的屏幕尺寸有且只有一種,也就是3.5英寸。開發app的時候,根本不用考慮同一個視圖在不同尺寸的屏幕上顯示的問題。iOS開發者完全可以用純代碼的方式把一個控件的frame寫死。
后來apple公司推出了4.0英寸的iPhone5和iPhone5S,所以,針對于不同尺寸的屏幕,再把控件的frame寫死就不可取了。(其實也不是不可取,很多iOS開發者做屏幕適配的時候不是用的autoresizing或autolayout,而是以代碼的方式動態獲取屏幕的尺寸,然后根據屏幕的尺寸來寫死子控件的frame。使用這種方式你會在代碼中無辜增加很多if...else... 的條件判斷語句。另一種方式是獲取到屏幕的尺寸后,按照控件和屏幕的比例來設置控件的frame,其本質上也是寫死frame。所以這兩種方式都不可取,畢竟將來會回出現越來越多的屏幕尺寸。從開發的角度,重復繁瑣的代碼會牽絆住開發者的進度;從程序設計角度,這樣的設計思路不夠高級,且日后不易于拓展和維護。)

三大適配技術

iOS屏幕適配主要有三個技術,分別是Autoresizing、AutoLayout、SizeClass。利用純代碼計算視圖的frame我們在此就不多介紹。至于什么是Autoresizing?什么是AutoLayout?什么是SizeClass?我們用SToryBoard的一張截圖可以說明問題,如下圖。

sizeclass 和 autolayout.png

Autoresizing和Auto Layout不能共存,所以如果使用Autoresizing,就不能勾選Use Auto Layout。

autoresizing.png

(一)Autoresizing

Autoresizing是早期iOS設備機型很少、APP界面布局相對簡單的背景下產生的一種屏幕適配技術。早期的iOS設備機型很少、屏幕尺寸單一、APP界面相對簡單,屏幕適配并沒有現在這么復雜,在當時這種背景下,產生了Autoresizing。當時這種情況下蘋果推出Autoresizing也是可以理解的,但是如果放到現在這種大背景下,Autoresizing是不能夠滿足開發者的屏幕適配需求的,具體原因請見下文。

1.1.Autoresizing的啟用

Xcode5之后,新建的項目默認使用AutoLayout。Autoresizing默認不啟用,我們可以去掉use Auto Layout前面的對勾來啟用Autoresizing,如下圖。


啟用Autoresizing.gif

1.2.Autoresizing介紹

有一種說法:autoresizing是為了解決iPad開發中橫豎屏適配問題應運而生的。代碼中的autoresizingMask和storyBoard中尺寸檢查器中的Autoresizing是一回事。iPhone5開始,Xcode添加了autolayout功能。storyBoard默認采用autolayout,取代了之前的autoresizing。如果使用autoresizing,需要在以下位置去掉“Use Auto Layout”。

1.2.1.storyboard中使用Autoresizing

storyboard中使用Autoresizing.gif

從上圖看出,storyBoard中的的Autoresizing只能設置兩個父子視圖之間的相對位置關系,一共6條虛線,分別是周圍的四條虛線和方塊內部的兩條線。周圍的四條虛線分別代表子控件距離父控件上、下、左、右之間的距離關系/或者叫約束關系,周圍的四條虛線所包圍的小方塊代表子視圖,小方塊內部的兩條帶雙向箭頭的線分別代表子控件的寬度和高度。
當我們點擊周圍四條虛線時,虛線會變成實線,代表子控件和父控件在這個方向上的間距被固定了。當我們點擊子視圖內部的虛線時,同樣也變為實線,代表子視圖的寬度或者高度被固定了。
舉個例子:當我們點擊最左邊的虛線時候,代表子視圖距離父視圖左邊的間距被固定了,而其他三個方向的距離和寬高會隨父視圖的縮放二縮放。

1.2.2.代碼中使用Autoresizing

我們不僅可以在storyboard中使用Autoresizing來約束父子視圖,也可以使用代碼來設置父子視圖之間的位置關系。UIView有一個autoresizingMask屬性,可以通過該屬性來約束父子視圖之前的位置關系,并且UIView還有一個BOOL類型的autoresizesSubviews屬性,默認為YES,代表父控件會跟隨子控件尺寸的變化而變化。和frame、bounds、center、transform等屬性一樣,autoresizingMask和autoresizesSubviews也是屬于UIView的幾何分類-UIViewGeometry中的屬性。

@property(nonatomic) BOOL autoresizesSubviews; // default is YES. if set, subviews are adjusted according to their autoresizingMask if self.bounds changes
@property(nonatomic) UIViewAutoresizing autoresizingMask; // simple resize. default is UIViewAutoresizingNone

查看注釋,autoresizesSubviews屬性的大意是:默認autoresizesSubviews = YES。如果UIView設置了autoresizesSubviews,那么他的子控件的bounds如果發生了變化,他的子控件將會根據子控件自己的autoresizingMask屬性的值來進行調整。
那么autoresizingMask又是什么呢?
autoresizingMask是一個枚舉值,作用是自動調整子控件與父控件中間的margin(間距)或者子控件的寬高。默認其枚舉值是UIViewAutoresizingNone。如下是其全部的枚舉值:

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { 
    UIViewAutoresizingNone = 0, 
    UIViewAutoresizingFlexibleLeftMargin = 1 << 0, 
    UIViewAutoresizingFlexibleWidth = 1 << 1, 
    UIViewAutoresizingFlexibleRightMargin = 1 << 2, 
    UIViewAutoresizingFlexibleTopMargin = 1 << 3, 
    UIViewAutoresizingFlexibleHeight = 1 << 4, 
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

從上面給出的代碼可以看出,autoresizingMask有7個枚舉值,除了UIViewAutoresizingNone之外還有6個枚舉值,分別對應storyboard中的那6條虛線。所以,storyboard和代碼是相同的,無論什么視圖,凡是可以通過storyboard進行設置的屬性,都有與之對應的屬性代碼,我們也可以使用代碼的方式實現。畢竟,storyboard中的屬性最終還是會翻譯成可執行的代碼,只不過XCode利用可視化的storyboard工具幫助我們完成了代碼完成的事情。
值得注意的是:autoresizingMask的枚舉值是使用位移的形式給出的,這樣設置的好處在于,當我們使用代碼給某個視圖設置autoresizingMask屬性時,我們可以給autoresizingMask屬性指定多個枚舉值。指定方式如下:

[subView setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin | 
![7.gif](http://upload-images.jianshu.io/upload_images/1055199-b48a0ccf7076c294.gif?imageMogr2/auto-orient/strip)
FlexibleLeftMargin];

甚至我們可以使用這種方式:

[subView setAutoresizingMask:5]; 

系統自動把5分解成5 = 4 + 1。然后我們就可以知道:4 = 1 << 2 = UIViewAutoresizingFlexibleRightMargin;1 = 1 << 0 = UIViewAutoresizingFlexibleLeftMargin。所以上面的代碼就被解釋成:

[subView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin];  // 子視圖距離父視圖左右間距不變,寬度隨父視圖寬度的縮放而縮放。

不難發現,這樣以位移的方式指定autoresizingMask枚舉值,也契合了storyboard中可以給子控件設置多個方向的約束的情景。如下圖:

storyBoard上的autoresizing.gif

注意:Autoresizing只能設置父子視圖之間的關系,也就是說,Autoresizing只能控制子視圖和父視圖之間的位置/大小關系。Autoresizing不能設置兄弟視圖之間的關系,當然也不能設置完全不相關的兩個視圖之間的關系。正因為Autoresizing只能設置父子視圖之間的關系,所以,Autoresizing只能應用于兩個視圖之間,不能應用于三個或者更多視圖之間。畢竟,一個兒子不可能有兩個親爹。
注意:UIView的autoresizesSubviews屬性為YES時(默認為YES),autoresizingMask才會生效。也就是說,當我們想要利用autoresizingMask指定某個控件和其父控件的關系時候,必須autoresizesSubviews = YES。
注意:值得注意的是,autoresizingMask的每個枚舉值都帶有Flexible這個單詞,其意思是:靈活的,彈性的。所以,我們在對其枚舉值進行一一翻譯。

      UIViewAutoresizingNone // 就是不自動調整。
  UIViewAutoresizingFlexibleLeftMargin // 自動彈性的調整與superView左邊的距離,保證與superView右邊的距離不變。
  UIViewAutoresizingFlexibleRightMargin // 自動彈性的調整與superView的右邊距離,保證與superView左邊的距離不變。
  UIViewAutoresizingFlexibleTopMargin // 自動彈性d調整與superView頂部的距離,保證與superView底部的距離不變。
  UIViewAutoresizingFlexibleBottomMargin // 自動彈性的調整與superView底部的距離,也就是說,與superView頂部的距離不變。
  UIViewAutoresizingFlexibleWidth // 自動彈性的調整自己的寬度,保證與superView左邊和右邊的距離不變。
  UIViewAutoresizingFlexibleHeight // 自動彈性的調整自己的高度,保證與superView頂部和底部的距離不變。

看完翻譯才恍然大悟,原來這些枚舉值和storyboard中的虛線是相反的,當我們點擊了storyboard中國的某個虛線后代表其間距被固定,而我們用代碼設置則代表相反方向的間距被固定。
但是,例外的是,storyBoard中子控件內部的兩個帶箭頭的虛線和枚舉值UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight具有相同的意義。也就是說,當我們點擊了storyBoard中子控件內帶箭頭的水平虛線使之變為實線時,就相當于[subView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];

[subView setAutoresizingMask-UIViewAutoresizingFlexibleWidth].gif
當我們點擊了storyBoard中子控件內帶箭頭的垂直虛線使之變為實線時,就相當于[subView setAutoresizingMask UIViewAutoresizingFlexibleHeight];
[subView setAutoresizingMask UIViewAutoresizingFlexibleHeight].gif

注意:簡而言之,4條margin虛線代表設置autoresizingMask中的與之對應的4枚舉值,而實線的width和height才代表設置autoreMask中與之對應的2個枚舉值。

1.2.3.Autoresizing的組合場景

autoresizingMask枚舉值及其對應的storyBoard預覽效果說明

UIViewAutoresizingMaskNone
view的frame不會隨superview的改變而改變,相當于frame(右圖的xib中預覽效果與實際效果有差,實際效果是view的上邊距不變)

UIViewAutoresizingMaskNone.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin
view和其superView左間距和上間距固定,寬高固定,右間距和底部間距隨父控件的縮放而按比例縮放


UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin
view與其superView上間距固定,右間距固定,寬高固定,左間距、下間距鎖父控件的縮放而縮放

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin
view與其superView的右間距、底部間距固定,寬高固定,上間距、左間距隨父控件的縮放而縮放


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin
view與其superView的左間距、底部間距固定,寬高固定,右間距、上間距隨父控件的縮放而縮放

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight

view與其superView的上間距、左間距、底部間距固定,寬度固定。高度、右邊距隨父控件縮放而縮放

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth
view與其superView的左間距、上間距、右間距固定,高度固定。寬度、底部間距隨父控件的縮放而縮放

UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight
view與其superView的上間距、右間距、底部間距固定,寬度固定。高度、左間距隨父控件的縮放而縮放

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth
view與其superView的左間距、右間距、底部間距固定,高度固定。寬度、上間距隨父控件的縮放而縮放

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth.gif

UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view與其superView的左間距、上間距、底部間距固定。寬度、高度、右間距隨父控件的縮放而縮放


UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view與其superView的左間距、上間距、右間距固定。寬度、高度、底部間距隨父控件的縮放而縮放


UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view與其superView的上間距、右間距、左間距固定。寬度、高度、左間距隨父控件的縮放而縮放

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
view與其superView的左間距、底部間距、右間距固定。寬度、高度、上間距隨父控件的縮放而縮放

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottom
view與其superView的寬高比例維持不變,上下左右間距也隨其superView的縮放而縮放

UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottom.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin
view與其superView的左右間距固定,高度固定,寬度、上間距、底部間距隨其父控件的縮放而縮放

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
view與其superView的上下左右邊距的比例維持不變,寬高固定,反映在storyBoard中,就是什么都不設置

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin
左邊距、右邊距、寬按比例調整,上邊距固定,下邊距固定,高度固定(右圖的xib中預覽效果與實際效果有差,實際效果是view的上邊距不變)垂直方向是同樣效果,故不列舉

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin.gif

UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
自動調整view的寬和高,保證上下左右邊距不變。如把tableView設置為此屬性,那么無論viewController的view是多大,都能自動鋪滿

UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.gif

不合理布局

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin
view與其superView的左邊距和右邊距的比例維持不變,上下間距固定,寬高固定(下圖的xib中預覽效果與實際效果有差,實際效果是view的上邊距不變)這種約束方式相當于上下間距固定,寬高固定,那么父控件高度縮放的時候就會產生沖突,所以這種布局方式是不合理的


UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin.gif
topAndBottom.gif

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin
view與其superView的上邊距和下邊距的比例維持不變,左右間距固定,寬高固定(這種約束方式相當于左右間距固定,寬高固定,那么父控件寬度縮放的時候就會產生沖突,所以這種布局方式也是不合理的)

UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin.gif
leftAndRight.gif

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth
view與其superView的左邊距和width按比例調整,高度固定,右邊距固定,上邊距固定,下邊距固定(下圖的xib中預覽效果與實際效果有差,實際效果是view的上邊距不變)(這種約束方式相當于上下間距固定,高度固定,那么父控件高度縮放的時候就會產生沖突,所以這種布局方式也是不合理的)

UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth .gif

綜上發現,只要是我們在水平方向同時固定了左邊距和右邊距,那么我們千萬不能固定子控件的寬度(反應在storyBoard中的設置,也就是必須使控制子控件寬度的虛線變為實線)。同理, 如果垂直方向同時固定了上邊距和下邊距,那么我們不能固定子控件的高度(反應在storyBoard中的設置,也就是必須使控制子控件高度的虛線變為實線)。

控制器的view的autoresizing

注意:如果我們在storyBoard中選中控制器的view,然后在尺寸檢查器中查看會發現,autoresizing中控制子控件的寬度和高度的虛線自動變成了實線(然而我并沒有點擊),這是因為控制器的view的寬高是一個默認值,默認和屏幕的尺寸相等,所以我們不能通過autoresizing來設置控制器的寬高。從另一個角度也能解釋:autoresizing是約束子控件和父控件之間的位置關系的,控制器的view并沒有父控件,所以不能通過autoresizing來約束控制器的view。

(二)AutoLayout

AutoLayout的前身是Autoresizing,也就是說,AutoLayout旨在替代Autoresizing。AutoLayout自iOS6開始引入,但由于當時XCode4當時對AutoLayout的支持不是很好,所以在XCode5/iOS7及其之后,AutoLayout才開始被廣泛應用。
注意:既然前面已經說了,AutoLayout旨在替代Autoresizing,所以在同一個項目中,AutoLayout和Autoresizing是不能共存的,二者只能選其一,如果你選擇了AutoLayout,那么Autoresizing自動被屏蔽掉;如果你選擇了Autoresizing,那么AutoLayout自動被屏蔽掉。XCode5及其之后的版本,默認新建的項目就是使用AutoLayout,不過我們可以在項目中進行更改,如下圖。

sizeclass和autolayout只能二選一.gif

Auto Layout is a Constraint-Based, Descriptive Layout System.翻譯過來大意是:autolayout是一個基于約束的、描述性的布局系統。autolayout之所以能夠進行屏幕適配,是因為他和autoresizing一樣,都是對屏幕上的控件相對位置的設置,而不是絕對位置。用蘋果官方的話,Auto Layout是一個基于約束的,描述性的布局系統。所謂基于約束就是代表我們可以為需要布局的子控件添加一些約束對象來限制他在屏幕上顯示的位置。所謂描述性是指其約束的設置可讀性較高,接近于人類語言。

1.約束
    每在Storyboard中對控件添加一個約束(autolayout的約束), 就代表添加一個約束對象。比如,給storyBoard中的某個子控件A設置了寬度和高度、距離父控件上下左右之間的間距,就相當于給這個控件添加了6個約束,也就產生了6個約束對象。
 2.約束錯誤(紅色箭頭)
    如果看到Storyboard中有紅色的箭頭, 代表約束有錯誤
    注意: 約束有錯誤, 不代表運行會錯誤, 約束有錯誤同樣可以運行
    注意: 紅色箭頭是程序員必須解決的
 
 3.為什么會有約束錯誤?
    3.1缺少約束
    3.2約束沖突
 
 3.1缺少約束
 >autolayout的本質和frame差不多
 >如果通過frame來設置一個控件, 必須設置這個控件的x/y/width/height, 控件才能按照我們的需求顯示
 >如果是通過autolayout來設置一個控件, 也必須設置這個控件的x/y/width/height, 控件才能按照我們的需求顯示
 >也就是說, 如果說x/y/wedth/height只要有一個沒有設置都會報錯, 就是缺少約束
 >因為autolayout對控件的約束是一種相對位置的約束,所以我們可以通過間接的方式來設置約束。比如,給某個子控件A設置了左邊距和右邊距后,雖然沒有明確指定子控件A的寬度,但是其左右邊距一旦設置,那么寬度可以根據子控件A和父控件左右之間的邊距自動推算出來。這就是我所說的間接的、相對位置的約束。
 3.2約束沖突
 >約束可以重復添加,但容易引發約束沖突
 >例如先約束某個子控件A的高度等于100,然后又給這個子控件A添加了一個高度約束, 約束高度等200, 那么這兩個約束就產生了沖突,控件A不知道他自己的高度是100還是200,所以就會產生約束沖突,系統就會報錯。
 

 紅色:
 距離頂部有20 == 相當于設置了Y
 距離左邊有20 == 相當于設置了x
 設置寬度等于100
 設置高度等于100
 
 4.約束警告
  如果看到Storyboard中有黃色的箭頭, 就是警告
 > 警告代表著當前控件在storyBoard中呈現的位置或者尺寸和程序運行后實際呈現的效果不一樣,導致約束警告的原因往往是沒有更新控件的約束,但并不影響其真實效果,也不會報錯。
*/

}

autolayout初步認識

對齊約束設置.png
約束設置.png

Top Layout Guide.png

注意:用storyBoard設置約束的時候,注意有一個Constrain to margins,默認打勾,也就是默認會給視圖添加一個20的左右邊距(上下不會添加),目的是讓視圖在iPhone6P和iPhone6SP上顯示的更好看一些。

Constrains to margins.gif

約束的添加規則

1.兩個同層級view之間的約束關系,添加到他們共同的父view上

Snip20160515_1.png

2.兩個不同層級上的view之間的約束關系,添加到他們最近的共同的父view上

Snip20160515_2.png

3.兩個有層次關系的view之間的約束關系,添加到層次較高的view上

Snip20160515_3.png

4.如果view的約束只和自己有關系,那么添加到自己身上。比如寬高約束。

UILabel使用AutoLayout

UILabel默認內容的顯示方式是垂直居中的。如果用autolayout給UILabel設置約束,只需要設置x、y、width,無需設置height,UILabel會自動包裹內容,并且隨內容的多小而變化。如果我們通過約束給定了UILabel的width = 100,但是內容仍然少的可憐,不能包裹,可以把寬度設置為<=100,此時,label的寬高都能包裹住內容。高度的設置同理可證。
當然,我們也可以只給UILabel設置x、y。但必須要保證UILabel的text屬性有內容,否則UILabel顯示不出來(這是初學者經常犯的錯誤)。原因在于,UILabel是根據內容自動調整寬度和高度,如果沒有內容,那么寬度和高度就是0,導致UILabel無法顯示。

父控件隨子控件變化而變化

如果希望父控件隨子控件(UILabel/UIView)高度的變化而變化,就不要給父控件添加高度約束,只需要子控和向父控件在垂直方向上添加約束,這樣子控件高度改變,父控件高度也會隨之改變。

AutoLayout動畫

事實上,我們在xib或者StoryBoard上給控件添加的約束,也是會被翻譯成代碼執行的。那么添加的那些約束會被翻譯成什么呢?本質上,在xib或者StoryBoard上添加的也U樹都是NSLayoutConstraint類型的對象。可以通過在StoryBoard上的控件和對應的.h或者.m文件之間拖線添加IBOutlet引用來證明。


#import "ViewController.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topConstraint;
@property (weak, nonatomic) IBOutlet UIView *customView;
@end

@implementation ViewController

- (void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
//    WSLog(@"%f", self.heightConstraint.constant);
    self.heightConstraint.constant += 10;
    self.topConstraint.constant += 100;
    [UIView animateWithDuration:2.0 animations:^{
        [self.customView layoutIfNeeded];
    }];
}
@end

純代碼給控件添加約束

如下圖,假設給控制器的view添加一個寬高均為100、水平、垂直居中的控件

Snip20160515_4.png

代碼如下:

 // 1.創建控件
    UIView *redView = [[UIView alloc] init];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
#warning 注意: 通過代碼給子控件添加Autolayout約束,必須要先把子控件添加到父控件身上!

    // 2.創建約束
#warning 注意點: 如果通過代碼來設置Autolayout約束, 那么必須先禁用Autoresizing
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    
    // 2.1寬度
    NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0 constant:100.0];
    // 將約束添加給自己
    [redView addConstraint:width];
    
    // 2.2高度
    NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0 constant:100.0];
    // 將約束添加給自己
    [redView addConstraint:height];
    
    // 2.3水平居中
    NSLayoutConstraint *xCos = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
    // 將約束添加到層次較高的父view上
    [self.view addConstraint:xCos];
    
    // 2.4垂直居中
    NSLayoutConstraint *yCos = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];
    // 將約束添加到層次較高的父view上
    [self.view addConstraint:yCos];

AutoLayout第三方開源框架-Masonry

Masonry是目前最流行的、最常用的AutoLayout的第三方開源框架。Masonry采用鏈式編程思想,極大的方便了開發者。大家可以在GitHub上找到Masonry。
同樣的問題,還是給控制器的view添加一個寬高均為100、水平、垂直居中的控件的Masonry的代碼,看起來就簡潔多了,Masonry代碼如下:

    // 1.創建一個控件
    UIView *redVeiw = [[UIView alloc] init];
    redVeiw.backgroundColor = [UIColor redColor];
    [self.view addSubview:redVeiw];
    
    // 2.禁止紅色View的Autgoresizing
    redVeiw.translatesAutoresizingMaskIntoConstraints = NO;
    
    [redVeiw mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.equalTo(100);
        make.height.equalTo(100);
        make.centerX.equalTo(self.view.mas_centerX);
        make.centerY.equalTo(self.view.mas_centerY);
    }];

注意:一般情況下,多數使用storyBoard中的autolayout設置約束,只有在萬不得已的情況下才用代碼設置autolayout約束,這種情況一般是,被約束的控件是代碼創建的,或者被約束的控件的父控件是代碼創建的。
本篇文章沒有講解蘋果自家的AutoLayout語言—VFL。因為筆者對VFL不使用不多,工作中幾乎沒有用到過,所以本篇文章就略過,感興趣的讀者可以自己研究。

(三)SizeClass

如下圖,就是sizeClass:


Snip20160515_5.png

iOS8/XCode6才開始引入的。SizeClass依賴于AutoLayout,也就是說,如果你的項目中用到了SizeClass,那么必須要使用AutoLayout。所以,不存在SizeClass和Autoresizing聯合使用的情況!
確切的說,sizeClass并不是一種屏幕布局技術,通過上面對autoresizing和autolayout的介紹,我們知道了autoresizing和autolayout是對屏幕上控件的相對位置進行的設置,也就是說,autoresizing和autolayout是針對于屏幕上的子控件而產生的技術。但sizeclass是對不同尺寸的屏幕的區分,sizeclass把不同尺寸(包括橫屏和豎屏)的屏幕進行了分類,無論是iPhone還是iPad設備,其寬度和高度都被劃分為三種類型:compact(緊湊)、regular(正常)、any(任意)我們只要針對于某一類型的屏幕進行布局,那么布局出來的界面可以顯示在屬于該類型的所有尺寸的屏幕上。

很多人認為sizeclass對屏幕寬度和高度的區分分為三種,compact(緊湊)、regular(正常)、any(任意)。這三種類型進行組合可以有9種結果。但在我看來,sizeclass對屏幕的區分只有兩種:compact和regular。any并不是真正的類型,他只是代表了compact和regular。正像runLoop中的commonMode一樣,并不是一種真正的mode,只是代表了defaultMode和trackMode。
sizeClass尺寸對照:

Snip20160515_6.png

一般情況下,StoryBoard中的sizeclass是(any,any)的。在sizeclass為(any,any)時布局的控件可以顯示在任何尺寸的設備上,包括所有尺寸的iPhone和iPad。如果我們選擇sizeclass為(compact,regular),那么在storyBoard上布局的控件只會顯示在寬度為“緊湊”,高度為“正常"狀態的設備上,也就是所有的“豎屏狀態”的iPhone上。橫屏狀態的iPhone不會顯示這個控件,橫屏和豎屏狀態的iPad也不會顯示這個子控件。
所以,當我們希望某個控件在橫屏是顯示,在豎屏時不顯示的時候,可以考慮有sizeClass這種技術。
并且,在iPad開發時,針對于同一界面,我們通常需要對橫豎屏的iPad分別進行布局,此時也可以使用sizeClass,我們只需要切換storyBoard底部的sizeClass就可以布局初互不干擾的界面。

在出現sizeClass技術之前,我們用xcode新建的universal項目默認會有兩個storyBoard,一個是專門為iPhone開發的storyBoard,另一個是專門為iPad開發的storyBoard。而在sizeClass出現之后,我們新建的universal項目就只有一個main.storyBoard。因為通過sizeClass我們可以在這一個main.storyBoard上為iPhone和iPad布局。

前面已經說過,我們選中sizeClass中一種屏幕類型,進行的布局只會出現在響應的設備上。比如,我選中w Compact H Regular。也就是寬度緊湊,高度正常。那么在這種狀態的storyBoard上布局的控件只會出現在豎屏的iPhone設備上,不會出現在橫屏的iPhone設備上。不信你繼續往下看:

3.1.W Compact H Regular(寬度緊湊 高度正常)

1>選中storyBoard上的sizeClass為W Compact H Regular。下面會有提示:For all iPhone in portrait。也就是在這個狀態下布局的控件只能出現在所有豎屏狀態的iPhone上!

W Compact H Regular.png

2>此時會發現storyBoard上的初始化控制器由原來的正方形(W Any H Any)變為iPhone狀態的長方形。
3>給storyBoard上的控制器添加一個水平、垂直居中、寬高都為150的紅色button。如下圖:

紅色button水平垂直居中.png

4>然后我們預覽在4英寸的iPhone設備上,橫屏和豎屏的顯示情況,如下圖:

sizeClass-W Compact H Regular.gif

從上圖,你會發現,當我把iPhone切換到橫屏狀態時,原本在豎屏顯示的紅色按鈕不見了。原因就在于,這個紅色按鈕是在sizeClass為W Compact H Regular狀態下添加給storyBoard上這個控制器的。這也驗證了我前面說過的,sizeClass為寬度緊湊,高度正常狀態時的布局智慧顯示在所有豎屏的iPhone上。當然,此處,我只是拿4.0英寸iPhone舉例,其他尺寸iPhone同理可證。在其他尺寸(3.5、4.7、5.5英寸)的橫屏狀態也不會顯示。當然,在iPad全屏(橫屏或豎屏)狀態下同樣不會顯示。因為iPad 的屏幕尺寸根本就不在 W Compact H Regular這一列。
但是,在iPad分屏狀態下是會顯示的。

3.2.W Regular H Compact(寬度正常 高度緊湊)

1>還是上面的那個storyBoard,還是上面的那個帶有紅色按鈕的控制器。我們現在把sizeClass切換為W Regular H Compact狀態。如下圖:

W Regular H Compact.png

2>你會發現,在設置sizeClass為W Regular H Compact時,下面的提示是:For 5.5-inch iphones in landscape。也就是說,在W Compact H Regular狀態下給控制器的View添加的子控件只會出現在5.5英寸的橫屏狀態下的iPhone上。同時你也會發現,在W Compact H Regular狀態下給控制器的view添加的紅色button不見了。如下圖:

橫屏.png

3>然后我們給控制器的view左上角(此處的左上角是相對于垂直狀態)添加一個綠色的button。如下圖:

左上角添加綠色button.png

4>然后我們預覽在5.5英寸的iPhone設備上,橫屏和豎屏的顯示情況,如下圖:

sizeClass-W Regular H Compact.gif

從上圖,你會發現,我們在W Regular H Compact狀態下給控制器左上角添加的綠色的按鈕只會顯示在橫屏狀態下,切換到豎屏狀態后,左上角什么都沒有。這也驗證了sizeClass為W Regular H Compact狀態下的布局會出現在5.5英寸的橫屏的iPhone上而不會出現在5.5英寸豎屏的iPhone上。
當然,切換到豎屏時,你同樣發現了控制器中間出現了一個紅色的按鈕,沒錯,這就是我們在上一個例子中(W Compact H Regular狀態)設置的那個水平、垂直居中的紅色button。這也再次驗證了,我們在不同的sizeClass下的布局并不會相互影響。
我們在W Regular H Compact狀態下的布局不是說好了“只會”出現在5.5英寸的橫屏狀態下嗎?上面只是驗證了會出現在5.5英寸的橫屏狀態的iPhone上,但是并沒有驗證“只會”這個詞語。請看下圖:

sizeClass-W Regular H Compact2.gif

上圖是以4.0英寸的iPhone設備為例進行的驗證,你會發現,在4.0英寸的設備處于橫屏狀態時,左上角并沒有出現綠色的button。至此,我們證明了sizeClass為W Regular H Compact時,在storyBoard上的控制器上的布局只會出現在5.5英寸的橫屏狀態的iPhone設備上。

3.3.W Regular H Regular(正常寬度 正常高度)

1>還是上面那個storyBoard,還是上面那個控制器,我們現在把sizeClass切換為W Regular H Regular,你會發現下面的提示:For iPads in portrait or landscape。也就是說,在sizeClass為W Regular H Regular狀態下的布局只會出現在所有橫屏或豎屏的iPad設備上,并不會出現在橫屏或者豎屏的iPhone設備上。

W Regular H Regular.png

2>和上面那個例子一樣,我們切換了sizeClass后,storyBoard上的控制器也變了形狀--變成了和iPad一樣方方正正的一個控制器。并且在左上角綠色的button不見了。"好像"變成了一個干干凈凈的控制器。

控制器.png

3>然后給控制器的右上角添加一個藍色的button,如下圖:

右上角添加藍色button.png

4> 然后我們預覽在iPad和iPhone設備上橫豎屏的顯示情況,如下圖:

  • iPad設備橫豎屏顯示情況,如下圖(因屏幕尺寸太小,需要滾屏,顯示效果不好):
sizeClass-W Regular H Regular.gif
  • iPhone設備橫豎屏顯示情況,如下圖:
sizeClass-W Regular H Regular in iPhone.gif

從上圖,可以看出,在iPhone上無論是橫屏還是豎屏,右上角都沒有顯示那個藍色的button。綜上,驗證了sizeClass為W Regular H Regular狀態時,在控制器上的布局只會顯示在iPad橫屏或者豎屏狀態下,而不會顯示在iPhone的橫屏或者豎屏狀態下。

總結

上面我列舉了三種sizeClass狀態下布局顯示情況,而按照sizeClass的九宮格組合情況來看,sizeClass應該有9個不同的組合。當然any代表了compact和regular(正常和緊湊)。比如,當我們選擇sizeClass為 W Regular H Any(寬度正常 高度任意)時,其實這代表了兩個不同的sizeClass:W Regular H Regular (寬度正常 高度正常)和 W Regular H Compact(寬度正常 高度緊湊)。也就是說,在sizeClass為 W Regular H Any(寬度正常 高度任意)狀態下的布局相當于在sizeClass為 W Regular H Regular(寬度正常 高度正常) 和W Regular H Compact(寬度正常 高度緊湊)布局之和。換句話說,在sizeClass為W Regular H Any(寬度正常 高度任意)下的布局的控件,不管高度如何,只要寬度正常就會顯示出來。

未完待續...

文/VV木公子(簡書作者)
PS:如非特別說明,所有文章均為原創作品,著作權歸作者所有,轉載轉載請聯系作者獲得授權,并注明出處,所有打賞均歸本人所有!

如果您是iOS開發者,或者對本篇文章感興趣,請關注本人,后續會更新更多相關文章!敬請期待!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 屏幕適配 本章節主要還是說明如何讓應用程序能夠適配在蘋果不同的屏幕和如何讓應用中的內容在不同的屏幕下能夠正常的放置...
    AlanGe閱讀 714評論 0 2
  • autoResizing autoLayout和sizeClass 1. autoResizing autores...
    鳴2010閱讀 720評論 1 5
  • 一、屏幕適配-autoresizing簡單使用 1、為什么要屏幕適配? > iphone手機屏幕尺寸呈現多樣化。 ...
    方圓十里不留母狗閱讀 536評論 0 0
  • 1.尺寸適配1.原因 iOS7中所有導航欄都為半透明,導航欄(height=44)和狀態欄(height=20)不...
    LZM輪回閱讀 6,135評論 1 4
  • 感恩天地滋養,宇宙永恒;感恩大自然無私的愛;感恩祖先傳承,歷代宗親護佑;感恩國泰民安,繁榮昌盛;感恩父母生養大恩,...
    天門金珠瑜伽閱讀 307評論 0 2