iOS-原生純代碼約束總結(二)之 AutoLayout


一,概述     

AutoLayout相比AutoResizing更加實用,是可以完全替代AutoResizing的一種自動布局方式。而在使用AutoLayout前,我們必須理解一個屬性,那就是translatesAutoresizingMaskIntoConstraints。該屬性表示autoresizingMask和autolayout兩種方式的轉換。這個屬性對於在代碼中生成的view來說默認是true,而對於IB中拖出來的view來說默認是false.
對於這一屬性,官方文檔給出的解釋是這樣的:

/* By default, the autoresizing mask on a view gives rise to constraints that fully determine 
 the view's position. This allows the auto layout system to track the frames of views whose 
 layout is controlled manually (through -setFrame:, for example).
 When you elect to position the view using auto layout by adding your own constraints, 
 you must set this property to NO. IB will do this for you.
 */
@property(nonatomic) BOOL translatesAutoresizingMaskIntoConstraints NS_AVAILABLE_IOS(6_0); // Default YES

從以上的描述中,我們可以知道在使用AutoResizing布局時,AutoLayout會根據autoResizing來創建同等行為的constraint出來確定視圖的位置。從而實現了視圖的自動布局。而當我們確定選擇使用AutoLayout添加自己的約束的時候,我們必須設置此屬性為NO,XIB中這個屬性默認是NO。
在實際的使用過程中,我還需要注意兩點:

1. 當我們設置這個屬性為YES的時候,view的布局結果由AutoResizingMask,frame,center這些因素共同決定,如果再在其上添加AutoLayout約束,自定義的AutoLayout約束就會和AutoResizing里     
   Autolayout約束沖突而報錯。

2.我們設置該屬性為NO,AutoResizing並不會直接失效,只有當我們為視圖設置了constraint之后,AutoResizing才會失效。

二,使用方法

   那么AutoLayout在開發中具體如何使用呢,這其實分為兩種情況,一種是借助xib中的約束功能通過連線的方法實現。還有一種就是代碼直接添加約束,但是代碼自動布局是一件很麻煩的事,我們通常又會借助第三方即Masonry。

   了解一下幾種約束

Width:對視圖寬度的約束
Height:對視圖高度的約束
Horizontal Spacing:對視圖間水平距離的約束
Vertical Spacing:對視圖間垂直距離的約束
Leading Space to Superview:與父視圖左邊界的約束
Trailing Space to Superview:與父視圖右邊界的約束
Top Space to Superview:與父視圖上邊界的約束
Bottom Space to Superview:與父視圖下邊界的約束
Widths Equally:視圖等寬約束
Heights Equally:視圖等高約束

 

第一種:借助xib中的約束功能通過連線的方法實現

1> Align:

英文解釋的意思為排序,那么我們即可以理解為,排序…點擊展開選項,我會挨個解釋一下。


* Leading Edges  選中的views們 左 對齊
* Trailing Edges   選中的views們 右 對齊
* Top Edges        選中的views們 上 對齊
* Bottom Edges   選中的views們 下 對齊
* Horizontal Centers   選中的views們 水平方向 中心對齊
* Vertical Centers       選中的views們 垂直方向 中心對齊
* Baselines                選中的views們基於 基線 對齊
* Horizontally in Container  選中的視圖 相對於 父視圖 進行 水平方向 對齊
* Vertical in Container        選中的視圖 相對於 父視圖 進行 垂直方向 對齊
* Update Frames None       再設置了這些約束之后不進行Frame的更改
* Update Frames Items of New Constraints   設置了這些約束之后 只更改選中的這些views的Frame
* Update Frames All Frames in Container      更改該 vc 里所有View的約束 (慎用!!!!!!)

2 > Pin:

英文解釋為 大頭針,其實我們就可以認為,這個展開項內的所有的現象都是為了將視圖訂在某一個位置的。那么我們再次展開選項


上方的輸入框 這個輸入框時表示當前選中的views或者view距離父視圖的上方距離為多少,左右下同理,不一一贅述了。

而點擊這個倒三角開啟的選項中
* User Standard Value :是說使用標准的的值,而且這個值只有在是設置上下方向的時候才有用。默認的值其實就是距離 Bottom Layout Guide,也就是上下基線。下基線就是距離視圖最底部。上基線就是距離StatusView下方的位置。而這里的默認值其實是8

* User Current Canvas Value : 使用當前位置設置。默認為當前設置方向最近的一個VIew,且沒有覆蓋遮擋的視圖
剩下的選項,會根據當前視圖的布置情況有所不同,但是道理相當,這個選項是讓你設置你要根據那個視圖進行當前位置距離的設置的

* Width 和 Height : 這個不需要我贅述了吧。分別是 寬度和高度 的設置

* Constarain to Margins : 這個是否需要外邊距 默認為 8 。一般沒啥卵用,都會去掉

* Equal Widths 和 Equal Heights : 這兩個選項需要選擇兩個視圖。比如選中View1和View2,那么可以分別設置這個兩個視圖寬度和高度相等

* Aspect Ratio 這個屬性是設置選中View的比例。當你點擊設置的時候,默認他會設置當前視圖的比例。比如你的View高度為40寬度為30。那么你的比例就將設置為3:4. 如果你希望修改這個比例的屬性,咱們在講完這三個東西之后,我會講解

* Align 這個東西你不得不承認這個東西和咱們學習的第一個Align重復的… 在這里就不贅述了

* Update Frames 這個查看上面 Align選項的這個屬性就好了

3 > Resole auto layout issues

他的這個解釋已經很好理解了,自動布局問題的解決。就是如果你在布局的
時候出現了一些問題。比如,咱們再設置約束之后,出現黃色或者紅色的的顏色的時候。就需要使用以下方式修改。紅色說明咱們設置的約束有缺失或者有沖突的問題,黃色則說明,約束正確,但是當前View的Frame和約束描述的Frame不一致


看到以上視圖咱們可以看出它分為兩個而且這兩種除了名字不一樣,選項是一摸一樣的額。

(1)  Selected Views : 這個說的就是你要處理的約束問題是當前你選中的View。


(2) All Views in View Controller : 則是說明要解決的約束問題是這個ViewController所有的VIew的(這個可得慎重的)。

     * Update Frames 修改Frame,當你的約束設置正確但是Frame不對的時候使用者選項可以講View的Frame展示成為約束所描述的樣子

     *  Update Constaints 而這個選項,說實話我沒用過。他的意思咱們也可以知道他是通過Frame 去修改 約束……

     *  Add Missing Constraints 添加缺失的約束,這個選項我也沒使用過,因為這個方法添加的缺失的約束不一定就是正確的約束,在實際運行中肯定會出現問題所以盡量自己把缺失的約束自己添加了。

     *  Reset to Suggested Constaints 重新設置建議的約束?沒使用過,不知道什么意思

     * Clear Constraints 清除約束,會刪除選中的視圖的所有的約束。在All Views in View Controller 你要是做這個選項的時候可得慎重,使用了就說明你要刪除當前VC所有的約束。當然你可以 ctrl-z

拓展

是使用XIB、StoryBoard好些還是使用純代碼布局好?

1、  在一些比較簡單、固定的界面。比如登錄、注冊或者其他只是進行內容展示的界面使用XIB、StoryBoard開發起來會更簡單快一些,這個時候我們也應該使用XIB、StoryBoard開發。

2、 在一些復雜、控件較多和功能多的界面盡量使用代碼進行布局開發。因為控件多功能復雜的界面如果使用XIB、StoryBoard。那么通過拉線的形式添加 約束布局,大家應該都有經歷過,一個XIB里拉 滿了密密麻麻的約束線,可以肯定的是過不了多久連自己都看暈了。如果這個模塊要交給第二個人維護,那么這些 密密麻麻的約束線肯定是一個讓人頭疼的問題。因為XIB中約束過多的話,首先可讀性是非常差的,帶來的后續問題是開發思路不清晰、維護難。

3、需要復用的模塊盡量使用代碼布局。如果使用XIB、StoryBoard則無法很好的對代碼模塊進行復用。

第二種:代碼直接添加約束

Auto Layout 中約束對應的類為 NSLayoutConstraint,一個 NSLayoutConstraint 實例代表一條約束。  (在iOS6之后,引入了autolayout這個概念,相應的也增加了NSLayoutConstraint這個對象,這個對象就是專門用來進行約束布局的設置對象。通過這個對象,我們可以設置類似視圖對象之間的間距,約束的寬高,比例等屬性。創建NSLayoutConstraint對象的方法有兩種:)

NSLayoutConstraint有兩個方法,我們主要介紹 constraintWithItem:也是最常用的。   

第一方式:創建NSLayoutConstraint對象

+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attribute1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attribute2 multiplier:(CGFloat)multiplier constant:(CGFloat)constant;

意思是:(描述的是一個view與另外一個view的位置和大小約束關系。)
           
             view1:要添加約束的視圖對象。
            view2:與之對應添加約束的視圖對象,例如,如過我要設置view1的上邊距離父視圖的上邊一定間距,這個view2就是view1的父視圖。
 attribute(屬性):有上、下、左、右、寬、高等。
  relation(關系):有(<=,==,>=)小於等於、等於、大於等於。(注意:小於等於或大於等於優先會使用等於關系,如果等於不能滿足,才會使用小於或大於。例如設置一個大於等於100 的關系,默認會是 100,當視圖被拉伸時,100 無法被滿足,尺寸才會變得更大。)
multiplier(比例):約束的比例,比如view1的寬是view2的寬的兩倍,這個multiplie就是2.

AutoLayout 的核心計算公式:

view1.attribute1 = view2.attribute2 * multiplier + constant value
item1 =(>=,<=) multiplier * item2 + constant。

舉個簡單的例子來說我們想設置第一個視圖的寬度是第二個視圖寬度的2倍,我們可以這樣寫:

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:view2 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:view1 attribute:NSLayoutAttributeWidth multiplier:2 constant:0]];

在這個方程式中,attribute1 和 attribute2 是自動布局可以調整時解決這些制約因素的變量。當你創建約束定義其他值。例如,如果您定義兩個按鈕的相對位置,第二個按鈕的起始位置距離第一個按鈕結束位置后8像素點。這種關系為線性方程如下所示:      

 button2.leading = 1.0 × button1.trailing + 8.0

例如一: 假如我們設計一個簡單的頁面。一個子view在父view中,其中子view的上下左右邊緣都離父view的邊緣40個像素。如下:

//設置背景顏色
self.view.backgroundColor = [UIColor redColor];
//創建子view
UIView *subView = [[UIView alloc]init];
//設置子view背景顏色
subView.backgroundColor = [UIColor blackColor];
//將子view添加到父視圖上
[self.view addSubview:subView];
//使用Auto Layout約束,禁止將Autoresizing Mask轉換為約束
[subView setTranslatesAutoresizingMaskIntoConstraints:NO];
//layout 子view
//子view的上邊緣離父view的上邊緣40個像素
NSLayoutConstraint *contraint1 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:40.0];
//子view的左邊緣離父view的左邊緣40個像素
NSLayoutConstraint *contraint2 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:40.0];
//子view的下邊緣離父view的下邊緣40個像素
NSLayoutConstraint *contraint3 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-40.0];
//子view的右邊緣離父view的右邊緣40個像素
NSLayoutConstraint *contraint4 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-40.0];
//把約束添加到父視圖上
NSArray *array = [NSArray arrayWithObjects:contraint1,contraint2,contraint3,contraint4, nil];
[self.view addConstraints:array];

注意事項:


(1)、添加約束前確定已經把需要布局的子view添加到父view上了
      (添加約束之前,一定要保證相關控件都已經在各自的父控件上。用上面的例子就是 [self.view addSubview:purpleView]; 一定要放在添加 left 約束之前,否則程序會 crash-崩潰,因為要確保  purpleView 要已經在 self.view 上了。建議先寫 [self.view addSubview:purpleView]; 之后,再專心寫約束。)

(2)、一定要禁止將Autoresizing Mask轉換為約束   view.translatesAutoresizingMaskIntoConstraints = NO;
      (要先禁止 autoresizing 功能,防止 AutoresizingMask 轉換成 Constraints,避免造成沖突,需要設置 view 的下面屬性為 NO:)

(3)、要把子view的約束加在父view上

(4)、因為iOS中原點在左上角所以使用offset時注意right和bottom用負數

(5)、不用再給 view 設置 frame

 

例如二: 子view在父view的中間,且子view長300,高200。

//設置背景顏色
[self.view setBackgroundColor:[UIColor redColor]];

//創建子view
UIView *subView = [[UIView alloc] init];

//設置子view背景顏色
[subView setBackgroundColor:[UIColor blackColor]];

//將子view添加到父視圖上
[self.view addSubview:subView];

//使用Auto Layout約束,禁止將Autoresizing Mask轉換為約束
[subView setTranslatesAutoresizingMaskIntoConstraints:NO];

//layout 子view

//子view的中心橫坐標等於父view的中心橫坐標 (水平居中)  
NSLayoutConstraint *constrant1 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];

//子view的中心縱坐標等於父view的中心縱坐標(垂直居中)
NSLayoutConstraint *constrant2 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0];

//子view的寬度為300 這里直接設置自身寬為300
NSLayoutConstraint *constrant3 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:300.0];

//設置自身寬,寬可以參照其他控件設置,比如是self.view寬的一半,則應該這樣寫

//參照其他控件設置位置 
 NSLayoutConstraint *contraint = [NSLayoutConstraint constraintWithItem:libraryView attribute:NSLayoutAttributeToprelatedBy:NSLayoutRelationEqual toItem:labelLibrary attribute:NSLayoutAttributeBottom multiplier:1.0 constant:20.0];

//距離父視圖底部40 NSLayoutConstraint *contraint = [NSLayoutConstraint constraintWithItem:btn attribute:NSLayoutAttributeBottomrelatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-40.0]; //居中顯示 NSLayoutConstraint *contraint = [NSLayoutConstraint constraintWithItem:clickLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:bgView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]; NSLayoutConstraint *contraint = [NSLayoutConstraint constraintWithItem:clickLabel attribute:NSLayoutAttributeCenterXrelatedBy:NSLayoutRelationEqual toItem:bgView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]; //子view的高度為200 NSLayoutConstraint *constrant4 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200.0]; //把約束添加到父視圖上 NSArray *array = [NSArray arrayWithObjects:contraint1,contraint2,contraint3,contraint4, nil]; [self.view addConstraints:array]; //可以直接用addConstraints [self.view addConstraints:@[[NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0], [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0], [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:200.0], [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:300.0]]];

 

注意事項:

(1)、如果是設置view自身的屬性,不涉及到與其他view的位置約束關系。 比如view自身的寬、高等約束時,方法constraintWithItem:的第四個參數view2(secondItem)應設為nil;且第五個參數attribute2(secondAttribute)應設為 NSLayoutAttributeNotAnAttribute 。

(2)、在設置寬和高這兩個約束時,relatedBy參數使用的是 NSLayoutRelationGreaterThanOrEqual,而不是 NSLayoutRelationEqual。
因為 Auto Layout 是相對布局,所以通常你不應該直接設置寬度和高度這種固定不變的值,除非你很確定視圖的寬度或高度需要保持不變。

三、添加約束(addConstraint)的規則

      在創建約束了之后,需要將其添加到作用的控件上才能生效,注意在添加約束的時候目標控件需要遵循以下規則(這里控件就用 view 表示):

(1)對於兩個同層級 view 之間的約束關系,添加到它們的父 view 上

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

(3)對於有層次關系的兩個 view 之間的約束關系,添加到層次較高的父 view 上

(4)對於比如長寬之類的,只作用在該 view 自己身上的話,添加到該 view 自己上。

可以看出,widthConstraint 和 Constraint 屬於第(4)種,leftConstraint 和 rightConstraint 屬於第(3)種。

例如四:

      topView在父視圖的左邊間距10,下邊間距-10,寬度為100,高度為20的實現代碼

UIView *topView = [[UIView alloc] init];

    topView.backgroundColor = [UIColor redColor];

    [self.view addSubview:topView];

    topView.translatesAutoresizingMaskIntoConstraints = NO;

    [self.view addConstraints:@[
           
                                 [NSLayoutConstraint constraintWithItem:topView

                                                             attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:10],

                                [NSLayoutConstraint constraintWithItem:topView

                                                             attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-10],

                                [NSLayoutConstraint constraintWithItem:topView

                                                             attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:100],

                                [NSLayoutConstraint constraintWithItem:topView

                                                             attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:20]

                                ]];

四、使用 Auto Layout (NSLayoutConstraint)實現動畫

     目前貌似還有很多人對於 Auto Layout 的動畫實現還不是很了解。畢竟以前我們處理動畫之類的交互大都是和view的frame屬性打交道,即使用傳統的 animation方法修改view的frame。大致類似於

CGRect newFrame = view.frame;
newFrame.size.height = 300;

[UIView animateWithDuration:3.0 animations:^{
view.frame = newFrame;
} completion:^(BOOL finished) {
}];

     那么在Auto Layout下我們又該如何處理相關的動畫呢?根據前面說到的Auto Layout布局約束的原理,在某個時刻約束也是會被還原成frame使視圖顯示,這個時刻可以通過layoutIfNeeded這個方法來進行控制,可 以立刻生成新的frame並展

//先根據初始化添加的約束生成最初的frame並顯示view

 [self.view layoutIfNeeded];

 [UIView animateWithDuration:3.0 animations:^{

  //遍歷查找view的heigh約束,並修改它
  NSArray *constrains = self.view.constraints;

  for (NSLayoutConstraint* constraint in constraints) {
    if (constraint.firstAttribute == NSLayoutAttributeHeight) {
        constraint.constant = 300;
    }
  }
//更新約束 在某個時刻約束會被還原成frame使視圖顯示

 [self.view layoutIfNeeded];

 } completion:^(BOOL finished) {

 }];

 這樣我們就可以通過 AutoLayout 實現傳統的animation方法。需要注意的是在調用animation方法之前一定要先調用layoutIfNeeded,這是為了讓view先生成最初的frame並顯示,否則動畫效果會失效。

例如1:

     在界面左下方放置一個寬高各為50的紅色View,它離左邊緣與下邊緣的間距都為20,在紅色方塊的右邊放一個離它20間距,離self.view底部也間距20,寬高相等的藍色方塊,然后需求:我將在藍色方塊的右邊再加個同樣大小的黃色方塊,然后,要求點擊屏幕,然后藍色方塊被移除,黃色方塊替代藍色方塊的位置!

//創建redView

UIView *redView = [[UIView alloc]init];
redView.backgroundColor = [UIColor redColor];
redView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:redView];

//創建redView第一個約束,相對self.view的左邊緣間距20
NSLayoutConstraint * redLeftLc = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeLeftMargin relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:20.0];

//只有在沒有參照控件的情況下,約束才加到自身,不然加到父控件上
[self.view addConstraint:redLeftLc];

//創建redView第二個約束,相對self.view的底邊緣間距20
NSLayoutConstraint *reomLc = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottomMargin multiplier:1.0f constant:-20];//由於是redview相對self.view往上減20,所以是-20

//添加約束

[self.view addConstraint:reomLc];

//創建redView第三個約束,設置自身寬,寬可以參照其他控件設置,比如是self.view寬的一半,則應該這樣寫

 

//這里直接設置自身寬為50
NSLayoutConstraint * redWLc = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0f constant:50.0f];

//由於沒有參照物,所以約束添加於自身身上
[redView addConstraint:redWLc];

//創建最后一個約束,自身的高
NSLayoutConstraint * redHLc = [NSLayoutConstraint constraintWithItem:redView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0f constant:50];

//由於沒有參照物,所以約束添加於自身身上
[redView addConstraint:redHLc];

 

//在紅色方塊的右邊放一個離它20間距,離self.view底部也間距20,寬高相等的藍色方塊

//先創建一個一個藍色的view添加到視圖上,剩下的就是用autolayout來設置它的“frame”了
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
blueView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:blueView];
self.blueView = blueView;

//創建第一個約束,左邊間距,由於是想要與紅色有20的間距,那么參照參數“toItem”就應該填redView
NSLayoutConstraint *blueLeft = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:redView attribute:NSLayoutAttributeRight multiplier:1.0f constant:20.0f];

//與其他控件發生約束,所以約束添加到父控件上

[self.view addConstraint:blueLeft];

//現在我們已經可以確定自己水平方向的位置了,還差垂直方向的位置,現在我們來創建第二個約束,參照物依然是紅色方塊,需求是要離self.view底部20間距,這正好和紅色一樣,可以直接與紅色方塊底部對齊.

NSLayoutConstraint *blueBottom = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:redView attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f];//與紅色方塊底部對齊,倍數1.0f.差值0.0f

//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:blueBottom];

//剩下兩個約束差不多,它們都以redView為參照,與其等寬等高

NSLayoutConstraint *blueW = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:redView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:0.0f];

[self.view addConstraint:blueW];
NSLayoutConstraint *blueH = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:redView attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f]; [self.view addConstraint:blueH];

  

其實Autolayout的思想非常簡單,剛開始使用的時候不要想着馬上一氣呵成,最好一個控件一個控件的實現依賴,分別滿足其位置與尺寸的需求,如果一下子幾個控件一起弄的話,你得思路非常清晰,往往大家犯錯是犯在約束添多了,而不是添少了。(就如上面的例子,很多人會在設置了與紅色等高等寬后,還同時去添加頂部對齊與底部對齊,這樣高度就重復設置了,他會忽略了,上下同時對齊不僅給予了垂直位置,也給予了高度,所以思路必須清晰!)

 

autolayout動畫需求:我將在藍色方塊的右邊再加個同樣大小的黃色方塊,然后,要求點擊屏幕,然后藍色方塊被移除,黃色方塊替代藍色方塊的位置

(這個例子可以再講一個autolayout的知識點:優先級(priority))

//先創建黃色View
UIView *yellowV = [[UIView alloc]init];

yellowV.backgroundColor = [UIColor yellowColor];

yellowV.translatesAutoresizingMaskIntoConstraints = NO;

[self.view addSubview:yellowV];

//開始創建約束,左間距約束
NSLayoutConstraint *yellowLeft = [NSLayoutConstraint constraintWithItem:yellowV attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:blueView attribute:NSLayoutAttributeRight multiplier:1.0f constant:20];

//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:yellowLeft];

//添加底部約束
NSLayoutConstraint *yellowBottom = [NSLayoutConstraint constraintWithItem:yellowV attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0f constant:-20];

//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:yellowBottom];

//這里直接設置寬高約束,就省事不加參照控件了.
NSLayoutConstraint *yellowW = [NSLayoutConstraint constraintWithItem:yellowV attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0f constant:50.0f];

[yellowV addConstraint:yellowW];

NSLayoutConstraint *yellowH = [NSLayoutConstraint constraintWithItem:yellowV attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:kNilOptions multiplier:1.0f constant:50.0f];

[yellowV addConstraint:yellowH];

//接下來我再黃色View添加一個約束,這個約束涉及到優先級.

//對黃色View添加約束,約束黃色view與紅色View的間距為20

NSLayoutConstraint *yellowAnotherLeft = [NSLayoutConstraint constraintWithItem:yellowV attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:redView attribute:NSLayoutAttributeRight multiplier:1.0f constant:20];

UILayoutPriority priority = 250;//設置優先級

yellowAnotherLeft.priority = priority;

//與其他控件發生約束,所以約束添加到父控件上
[self.view addConstraint:yellowAnotherLeft];

想必大家應該看出些端倪,我在前面已經給黃色View添加了對藍色View間距位20的view,現在又給黃色view對紅色View添加一個間距20的約束,這很明顯是不可能出現的情況,黃色View怎么可能同時做到這兩個約束呢,用術語來說就是約束沖突,但是大家注意看這段代碼:

UILayoutPriority priority = 250;//設置優先級

我給yellowAnotherLeft這個約束添加了優先級,優先級的范圍是0~1000,數字越大,優先級越高,在不設置的情況下默認為1000.

這說明了,我最后添加的這個約束的優先級是低的,這個約束只有在它的沖突約束被抹掉后,它才能實現

也就是說,我把藍色view移除后,黃色View相對於藍色View左間距20這個約束就不成立了,那么黃色view會自動的變為與紅色View間距20

 

//去除藍色方塊

//現在大家明白優先級是什么情況了吧!最后加幾行代碼,來實現這個動畫吧!

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

//先把藍色方塊從父視圖上移除

    [self.blueView removeFromSuperview];

//動畫更新界面

    [UIView animateWithDuration:1.0f animations:^{

        [self.view layoutIfNeeded];

    }];

} 

autolayout的動畫就是這樣實現的,將操作代碼走完后,再讓動畫塊去更新界面,動畫就出來了

五、更新/修改約束

      Auto Layout 的更新、修改約束操作只有constant屬性可以修改,其它都是只讀的。所以對於現有約束的修改和更新都是圍繞NSLayoutConstraint實例的constant屬性展開的。

1、如果你是使用 Xib/StoryBoard 的話,在程序運行時想動態的改變視圖的約束這樣做:

約束Constraint也可以像控件一樣做IBOutlet鏈接,通過拖線關聯到文件。(事例這里是改變子view相對於父view的top約束)

首先在你的ViewController的頭文件里定義一個約束屬性:

@property (nonatomic, weak)  IBOutlet NSLayoutConstraint *topConstraint ;

然后在XIB里選中 File's Owner,選中Outlet欄目。將對應的Outlet拖動到View對應Constraint連接起來就可以了。跟button/label做鏈接一摸一樣的。這樣我們就可以獲得view上面的某個具體約束了,然后就可以在文件中對這個約束進行我們想要的修改。 

//更新約束
self.topConstraint.constant = 10;

 

總結來說就是:拖線關聯到文件獲得約束,修改約束的constant屬性。

 

2、如果你是使用 Xib/StoryBoard ,但是不想通過上述拉線的方式獲得約束然后再去更新它,你還可以采用代碼的方式修改約束:

但是無論采用哪種方式,我們都要遵循Auto Layout 的約束更新機制:想要更新視圖上面的約束,就要先找到對應的約束再去更新它。遍歷view上面的所有約束,查找到要更新的約束再進行更新。

所以我們要像上面一樣要先獲得top約束。在代碼中的體現就是通過約束的標識字段,在其父view的constraints數組中遍歷查找。這是因為每個view的constraints數組中保存的實際上是 layout 子view所需的約束的集合。

我們可以通過下面的輔助函數實現:

NSArray *constrains = self.view.constraints;
for (NSLayoutConstraint* constraint in constrains) {

 if (constraint.firstAttribute == NSLayoutAttributeTop) {
    constraint.constant = 10;
 }
}

這里只判斷了一個標識字段(NSLayoutAttributeTop),但是大多數情況下view上面的約束依賴不會那么簡單,所以需要查找判斷多個標識字段,才能找到。

 

3、如果你是使用代碼布局,那就用上面2介紹的方法更新約束,或者在添加約束之前先將該約束記錄下來,方便后面的更新修改。

 

六、Auto Layout 關於更新約束的幾個方法

* setNeedsLayout:告知頁面需要更新,但是不會立刻開始更新。執行后會立刻調用layoutSubviews。

* layoutIfNeeded:告知頁面布局立刻更新。所以一般都會和setNeedsLayout一起使用。如果希望立刻生成新的frame需要調用此方法,利用這點一般布局動畫可以在更新布局后直接使用這個方法讓動畫生效。

* layoutSubviews:系統重寫布局。

* setNeedsUpdateConstraints:告知需要更新約束,但是不會立刻開始。

* updateConstraintsIfNeeded:告知立刻更新約束。

* updateConstraints:系統更新約束。

這么多方法中,目前使用比較多的是 layoutIfNeeded 。因為在Auto Layout 實現動畫的時候,layoutIfNeeded 方法可以立刻生成新的frame特性是一大利器。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM