隨著iPhone6/6+設備的上市,如何讓手頭上的APP適配多種機型多種屏幕尺寸變得尤為迫切和必要。(包括:iPhone4/4s,iPhone5/5s,iPhone6/6s,iPhone 6p/6ps)。
在iPhone6出現以前,我們接觸的iPhone屏幕只有兩種尺寸:320 x 480和320 x 568。所以在那個時候使用傳統的絕對定位(Frame)方式進行界面控件的布局還是比較輕松的,因為我們只需要稍微調整一下Frame就可以適配這兩種大小的屏幕了。也許這也是為什麼雖然AutoLayout從IOS6就已經出現了,但是對於AutoLayout的使用和普及好像都不怎麼火熱。不過直到最近隨著iPhone6/6+設備的出現,AutoLayout又被眾多開發者重新審視和重視了。畢竟APPLE推出AutoLayout就是為了幫助開發者的APP更方便簡單的適配將來不同蘋果設備的不同大小屏幕。
首先我們來看一下APPLE官方是如何描述Auto Layout的:Auto Layout 是一個系統,可以讓你通過創建元素之間關系的數學描述來布局應用程序的用戶界面,是一種基於約束的,描述性的布局系統。所以我們現在要開始摒棄使用傳統的設置 frame 的布局方式的思維來開發視圖界面了。因為在 Auto Layout 中,當你描述完視圖對象之間的約束之後, Auto Layout 會自動幫你計算出視圖對象的位置和大小,也就間接的設定了視圖的Frame。反過來,如果我們還使用傳統的絕對定位的方式,通過設定視圖的Frame來布局的話,那麼隨著蘋果設備屏幕尺寸的碎片化,那麼每一種屏幕尺寸都要給界面控件設定一套合適該尺寸的Frame,這種方式想想就夠嚇人的!另外還需要說明的是,如今確實還有不少人仍然使用設定Frame的方式進行布局,並且通過取設備屏幕的寬高進行一定比例的換算確實可以達到正確的定位布局,但是在大多數情況下,如果頁面支持屏幕旋轉的話,這種設定Frame的方式就完全失效了,旋轉屏幕需要做大量的額外處理。還有一點是我們必須注意的,很多人習慣上是在viewdidload方法中初始化控件(包括init 和設置frame),但是viewController要在viewWillLayoutSubviews的時候才能真正確定view和子view的frame。所以在沒有確定view的frame的時候就去操作修改view的frame是不友好的,設置frame的效果也是無法預料的。所以按照這個特性,如果我們用設置Frame的方式布局的話就必須在viewWillLayoutSubviews中重新設定view的坐標大小,布局邏輯會變得更不清晰。而使用AutoLayout則不會有這些問題。
那麼接下來我們來講一下如何使用AutoLayout。
大家都應該清楚,我們可以在XIB、StoryBoard中通過拉線的形式給控件視圖添加布局約束,通過蘋果強大的可視化界(Interface Builder)我們能夠輕松的使用AutoLayout完成界面視圖的布局。另外一種方式就是通過純代碼的形式使用AutoLayout,即NSLayoutConstraint。本人是個代碼控,個人比較傾向於代碼寫界面,所以本文主要講一下最近本人通過純代碼的方式使用AutoLayout和使用第三方界面布局庫Masonry進行代碼布局的總結和分享。
首先談一下在如今AutoLayout的時代,是使用XIB、StoryBoard好些還是使用純代碼布局好!?本人根據自己的經驗覺得,這個沒有一個絕對的界限或者什麼一刀切。但是在權衡這個問題的時候,我個人覺得有幾個原則應該要去遵守的:
1、在一些比較簡單、固定的界面。比如登錄、注冊或者其他只是進行內容展示的界面使用XIB、StoryBoard開發起來會更簡單快一些,這個時候我們也應該使用XIB、StoryBoard開發。
2、在一些復雜、控件較多和功能多的界面盡量使用代碼進行布局開發。因為控件多功能復雜的界面如果使用XIB、StoryBoard。那麼通過拉線的形式添加約束布局,大家應該都有經歷過,一個XIB裡拉滿了密密麻麻的約束線,可以肯定的是過不了多久連自己都看暈了。如果這個模塊要交給第二個人維護,那麼這些密密麻麻的約束線肯定是一個讓人頭疼的問題。因為XIB中約束過多的話,首先可讀性是非常差的,帶來的後續問題是開發思路不清晰、維護難。
3、需要復用的模塊盡量使用代碼布局。如果使用XIB、StoryBoard則無法很好的對代碼模塊進行復用。
NSLayoutConstraint 篇
進入正題,我們首先來談一下如何使用官方提供的API(NSLayoutConstraint)進行代碼布局。
談到NSLayoutConstraint,大家都有一個不怎麼好的感覺。哎,可以肯定的是APPLE一直在推AutoLayout。只是貌似在可視化的布局設計(XIB、StoryBoard)下的力度和功夫遠比代碼布局要大。因為通過APPLE提供的API進行代碼布局確實不怎麼好用,但是還是在可以接受的范圍,呵呵!
一、Autoresizing Mask
在使用AutoLayout之前我們先介紹Autoresizing Mask。
必須要注意的是在使用 Auto Layout 時,首先需要將視圖的 setTranslatesAutoresizingMaskIntoConstraints 屬性設置為 NO。這個屬性默認為 YES。當它為 YES 時,運行時系統會自動將 Autoresizing Mask 轉換為 Auto Layout 的約束,這些約束很有可能會和我們自己添加的產生沖突。 我們常常會忘了做這一步,然後引起的約束報錯就是這樣的:
所以如果你是使用 Xib/StoryBoard 的話,系統會自動幫你把這個屬性設置為 NO。通過Interface Builder,打開某個Xib或者StoryBoard,在右側Show in file inspector裡面就能看到Ues Autolayout選項,系統默認將其勾選。如下圖:
即在XIB上開啟Autolayout後,autoresizingMask就被廢棄了。避免了約束沖突的情況。
如果你是通過代碼布局的話,在給view添加約束之前,只需要通過代碼把view的 setTranslatesAutoresizingMaskIntoConstraints 屬性設置為 NO。
[objc] view plaincopy [view setTranslatesAutoresizingMaskIntoConstraints:NO];
二、NSLayoutConstraint
Auto Layout 中約束對應的類為 NSLayoutConstraint,一個 NSLayoutConstraint 實例代表一條約束。
NSLayoutConstraint有兩個方法,我們主要介紹 constraintWithItem:也是最常用的:
[objc] view plaincopy +(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;
這個API給我們的第一印象就是參數有點多。其實仔細一看表達的意思無非就是:view1的某個屬性(attr1)等於view2的某個屬性(attr2)的值的多少倍(multiplier)加上某個常量(constant)。描述的是一個view與另外一個view的位置和大小約束關系。其中屬性attribute有上、下、左、右、寬、高等,關系relation有小於等於、等於、大於等於。需要注意的是,小於等於 或 大於等於 優先會使用 等於 關系,如果 等於 不能滿足,才會使用 小於 或 大於。例如設置一個 大於等於100 的關系,默認會是 100,當視圖被拉伸時,100 無法被滿足,尺寸才會變得更大。
那麼下面我們來看一下,如何運用NSLayoutConstraint進行代碼布局。
場景一:
假如我們設計一個簡單的頁面。一個子view在父view中,其中子view的上下左右邊緣都離父view的邊緣40個像素。這個我們該如何寫呢?如下:
[objc] view plaincopy [self.view setBackgroundColor:[UIColor redColor]]; //創建子view UIView *subView = [[UIView alloc] init]; [subView setBackgroundColor:[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 nil]; [self.view addConstraints:array];
效果如圖:
這裡講一下比較容易犯錯的地方:
1、添加約束前確定已經把需要布局的子view添加到父view上了
2、一定要禁止將Autoresizing Mask轉換為約束
3、要把子view的約束加在父view上
4、因為iOS中原點在左上角所以使用offset時注意right和bottom用負數
場景二:
子view在父view的中間,且子view長300,高200。
[objc] view plaincopy [self.view setBackgroundColor:[UIColor redColor]]; //創建子view UIView *subView = [[UIView alloc] init]; [subView setBackgroundColor:[UIColor blackColor]]; [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 NSLayoutConstraint *constrant3 = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:300.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:constrant1, constrant2, constrant3, constrant4, nil nil]; [self.view addConstraints:array];
效果如圖:
這裡需要注意的是:
1、如果是設置view自身的屬性,不涉及到與其他view的位置約束關系。比如view自身的寬、高等約束時,方法constraintWithItem:的第四個參數view2(secondItem)應設為
nil;且第五個參數attr2(secondAttribute)應設為 NSLayoutAttributeNotAnAttribute 。
2、在設置寬和高這兩個約束時,relatedBy參數使用的是 NSLayoutRelationGreaterThanOrEqual,而不是 NSLayoutRelationEqual。因為 Auto Layout 是相對布局,所以通常你不應該直接設置寬度和高度這種固定不變的值,除非你很確定視圖的寬度或高度需要保持不變。
三、更新/修改約束
Auto Layout 的更新、修改約束操作,也不怎麼友好和方便。
先來看一下 NSLayoutConstraint 的API,貌似只有constant屬性可以修改,其它都是只讀的。所以對於現有約束的修改和更新都是圍繞NSLayoutConstraint實例的constant屬性展開的。
1、如果你是使用 Xib/StoryBoard 的話,在程序運行時想動態的改變視圖的約束,你可以這樣做:
約束Constraint也可以像控件一樣做IBOutlet鏈接,通過拖線關聯到文件。(事例這裡是改變子view相對於父view的top約束)
首先在你的ViewController的頭文件裡定義一個約束屬性:
@property (nonatomic, weak) IBOutlet NSLayoutConstraint *topConstraint ;
[objc] view plaincopy #import @interface LayoutConstraintViewController : UIViewController @property (nonatomic, weak) IBOutlet NSLayoutConstraint *topConstraint; @end
然後在XIB裡選中 File's Owner,選中Outlet欄目。將對應的Outlet拖動到View對應Constraint連接起來就可以了。跟button/label做鏈接一摸一樣的。
這樣我們就可以獲得view上面的某個具體約束了,然後就可以在文件中對這個約束進行我們想要的修改。
[objc] view plaincopy //更新約束 self.topConstraint.constant = 10;
總結來說就是:拖線關聯到文件獲得約束,修改約束的constant屬性。
2、如果你是使用 Xib/StoryBoard ,但是不想通過上述拉線的方式獲得約束然後再去更新它,你還可以采用代碼的方式修改約束:
但是無論采用哪種方式,我們都要遵循Auto Layout 的約束更新機制:想要更新視圖上面的約束,就要先找到對應的約束再去更新它。遍歷view上面的所有約束,查找到要更新的約束再進行更新。
所以我們要像上面一樣要先獲得top約束。在代碼中的體現就是通過約束的標識字段,在其父view的constraints數組中遍歷查找。這是因為每個view的constraints數組中保存的實際上是 layout 子view所需的約束的集合。
我們可以通過下面的輔助函數實現:
[objc] view plaincopy 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特性是一大利器。
五、使用 Auto Layout (NSLayoutConstraint)實現動畫
目前貌似還有很多人對於 Auto Layout 的動畫實現還不是很了解。畢竟以前我們處理動畫之類的交互大都是和view的frame屬性打交道,即使用傳統的 animation方法修改view的frame。大致類似於
[objc] view plaincopy 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並展示出來,從而實現動畫效果。
[objc] view plaincopy //start animations //先根據初始化添加的約束生成最初的frame並顯示view [self.view layoutIfNeeded]; [UIView animateWithDuration:3.0 animations:^{ //遍歷查找view的heigh約束,並修改它 NSArray *constrains = self.view.constraints; for (NSLayoutConstraint* constraint in constrains) { if (constraint.firstAttribute == NSLayoutAttributeHeight) { constraint.constant = 300; } } //更新約束 在某個時刻約束會被還原成frame使視圖顯示 [self.view layoutIfNeeded]; } completion:^(BOOL finished) { }];
這樣我們就可以通過 AutoLayout 實現傳統的animation方法。需要注意的是在調用animation方法之前一定要先調用layoutIfNeeded,這是為了讓view先生成最初的frame並顯示,否則動畫效果會失效。
Masonry 篇
Masonry是一個輕量級的界面布局框架,擁有自己的描述語法,采用更優雅的鏈式語法封裝自動布局,簡潔明了並具有高可讀性,而且同時支持 iOS 和 Max OS X。Masonry是一個用代碼寫iOS或OS界面的庫,用官方的說明就是Masonry完成可以代替Auto layout。Masonry的github地址:https://github.com/SnapKit/Masonry
Masonry使用起來很方便和流暢,本人最近開始在新項目中使用框架進行界面布局。親身的實踐感覺Masonry確實比APPLE的官方的API(NSLayoutConstraint)好用很多。先來看一下Masonry官方的提供的sample
code:
[objc] view plaincopy [view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(superview).with.insets(padding); }];
這也是最常用的用法,為view設置約束。 看到上面的代碼風格,典型的鏈式語法,流暢易懂。
我們先來看一下Masonry支持的約束屬性:
[objc] view plaincopy // 左側 @property (nonatomic, strong, readonly) MASConstraint *left; // 頂部 @property (nonatomic, strong, readonly) MASConstraint *top; // 右側 @property (nonatomic, strong, readonly) MASConstraint *right; // 底部 @property (nonatomic, strong, readonly) MASConstraint *bottom; // 首部 @property (nonatomic, strong, readonly) MASConstraint *leading; // 尾部 @property (nonatomic, strong, readonly) MASConstraint *trailing; // 寬 @property (nonatomic, strong, readonly) MASConstraint *width; // 高 @property (nonatomic, strong, readonly) MASConstraint *height; // 中心點x @property (nonatomic, strong, readonly) MASConstraint *centerX; // 中心點y @property (nonatomic, strong, readonly) MASConstraint *centerY; // 文本基線 @property (nonatomic, strong, readonly) MASConstraint *baseline;
這裡需要注意的是:NSLayoutAttributeLeft/NSLayoutAttributeRight 和 NSLayoutAttributeLeading/NSLayoutAttributeTrailing的區別是left/right永遠是指左右,而leading/trailing在某些從右至左習慣的地區會變成,leading是右邊,trailing是左邊。所以如果涉及到國際化方面,建議還是使用 NSLayoutAttributeLeading/NSLayoutAttributeTrailing。
在Masonry中能夠添加、修改 Auto layout 約束有三個函數:
[objc] view plaincopy
(NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block; (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block; (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
其中:
mas_makeConstraints 只負責新增約束,Autolayout不能同時存在兩條針對於同一對象的約束,否則會報錯。(這個方法最常用)
mas_updateConstraints 針對上面的情況會更新在block中出現的約束,不會導致出現兩個相同約束的情況。
mas_remakeConstraints 則會清除之前的所有約束 僅保留最新的約束。
如果我們靈活的運用這三個方法,基本就可以應付各種各樣的約束布局情況了。
一、添加約束(mas_makeConstraints)
先來看一下Masonry如何實現一個view的簡單布局。
場景一:
還是和上面的例子一樣:一個子view在父view中,其中子view的上下左右邊緣都離父view的邊緣40個像素。
[objc] view plaincopy [self.view setBackgroundColor:[UIColor redColor]]; //創建子view UIView *subView = [[UIView alloc] init]; [subView setBackgroundColor:[UIColor blackColor]]; [self.view addSubview:subView]; //layout 子view __weak __typeof(self)weakSelf = self; [subView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(weakSelf.view).with.offset(40);//子view的上邊緣離父view的上邊緣40個像素 make.left.equalTo(weakSelf.view).with.offset(40);//子view的左邊緣離父view的左邊緣40個像素 make.bottom.equalTo(weakSelf.view).with.offset(-40);//子view的下邊緣離父view的下邊緣40個像素 make.right.equalTo(weakSelf.view).with.offset(-40);//子view的右邊緣離父view的右邊緣40個像素 }];
針對上面的布局約束寫法,還有更為簡潔的寫法:
[objc] view plaincopy //layout 子view [subView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(weakSelf.view).insets(UIEdgeInsetsMake(40, 40, 40, 40)); }];
效果如下:
可以很明顯的看出,使用Masonry布局無論是代碼量還是語法描述都很簡潔易懂。比起前面使用 NSLayoutConstraint 不是好一點半點。
場景二:
子view在父view的中間,且子view長300,高200。
[objc] view plaincopy [self.view setBackgroundColor:[UIColor redColor]]; //創建子view UIView *subView = [[UIView alloc] init]; [subView setBackgroundColor:[UIColor blackColor]]; [self.view addSubview:subView]; //layout 子view __weak __typeof(self)weakSelf = self; [subView mas_makeConstraints:^(MASConstraintMaker *make) { make.center.equalTo(weakSelf.view);//子view在父view中間 make.size.mas_equalTo(CGSizeMake(300, 200));//子view長300,高200 }];
效果如下:
二、更新、修改約束(mas_updateConstraints)
使用Masonry更新約束非常方便簡單。
比如需要將上面例子的view的寬度和高修都改為100:
[objc] view plaincopy //將view的寬度、高度設為100 [subView mas_updateConstraints:^(MASConstraintMaker *make) { make.width.equalTo(@100); make.height.equalTo(@100); }];
三、在使用Masonry中,我們需要注意幾個問題
1、在使用 mas_makeConstraint 方法給view添加約束的時候,必須要確定該view已經添加到父view上了,即[self.view addSubview:view];否則將會約束報錯。這個和使用NSLayoutConstraint一樣。
2、Autolayout不能同時存在兩條針對於同一對象的約束,否則會報錯。只能進行更新修改。
3、其次對於 equalTo 和 mas_equalTo的區別:mas_equalTo只是對其參數進行了一個BOX操作(裝箱) ,所支持的類型除了NSNumber支持的那些數值類型之外就只支持CGPoint、CGSize、UIEdgeInsets,例如:make.size.mas_equalTo(CGSizeMake(300,400));
對於對象或是多個屬性的處理,就使用equalTo,例如:make.size.equalTo(weakSelf.view); make.width.equalTo(weakSelf.view); make.height.equalTo(@30);
4、方法with和and,這連個方法其實沒有做任何操作,方法只是返回對象本身,這這個方法的左右完全是為了方法寫的時候的可讀性 。
5、因為iOS中原點在左上角所以使用offset時注意right和bottom用負數。
6、Masonry約束是無法更新 NSLayoutConstraint 約束。因為Masonry在更新約束的時候會去遍歷查找view上面的約束集,先判斷view上的約束的類是否為 MASLayoutConstraint的類,如果是才會進行更新。所以,如果你是用XIB、StoryBoard拉線添加的約束或者是通過代碼方式使用NSLayoutConstraint類添加的約束都無法在代碼裡用Masonry的 mas_updateConstraints 方法進行約束更新。Masonry更新約束的部分源碼:
[objc] view plaincopy - (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint { // check if any constraints are the same apart from the only mutable property constant // go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints // and they are likely to be added first. for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) { if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue; if (existingConstraint.firstItem != layoutConstraint.firstItem) continue; if (existingConstraint.secondItem != layoutConstraint.secondItem) continue; if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue; if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue; if (existingConstraint.relation != layoutConstraint.relation) continue; if (existingConstraint.multiplier != layoutConstraint.multiplier) continue; if (existingConstraint.priority != layoutConstraint.priority) continue; return (id)existingConstraint; } return nil; }
四、Masonry實現動畫
Masonry的動畫實現和NSLayoutConstraint類的動畫實現基本一致,都是基於 layoutIfNeeded 方法。傳統的 animation方法通過Masonry實現如下:
[objc] view plaincopy [self.view setBackgroundColor:[UIColor redColor]]; //創建子view UIView *subView = [[UIView alloc] init]; [subView setBackgroundColor:[UIColor blackColor]]; [self.view addSubview:subView]; //layout 子view __weak __typeof(self)weakSelf = self; [subView mas_makeConstraints:^(MASConstraintMaker *make) { //子view的上邊緣離父view的上邊緣100個像素 make.top.equalTo(weakSelf.view).with.offset(100); //子view的左邊緣離父view的左邊緣50個像素 make.left.equalTo(weakSelf.view).with.offset(50); //子view的右邊緣離父view的右邊緣50個像素 make.right.equalTo(weakSelf.view).with.offset(-50); //子view的高度為1 make.height.equalTo(@1); }]; //先根據初始化添加的約束生成最初的frame並顯示view [self.view layoutIfNeeded]; //開始動畫 [UIView animateWithDuration:3.0 animations:^{ [subView mas_updateConstraints:^(MASConstraintMaker *make) { make.height.equalTo(@300); }]; //更新約束 在某個時刻約束會被還原成frame使視圖顯示 [self.view layoutIfNeeded]; } completion:^(BOOL finished) { }];
參考:
http://www.cocoachina.com/ios/20150702/12217.html
http://www.cocoachina.com/ios/20141219/10702.html
http://xuexuefeng.com/autolayout/
https://github.com/ming1016/study/wiki/Masonry