前言
好久沒有寫Blog了,這段時間有點忙。
本文舉了3個比較有“特點”的Autolayout例子,源於微博上好友的提問,感覺比較有意思,也比較有代表性,就寫了出來,分享給大家~
至於為什麼用Masonry,那是因為它好用啊!(被問到過有關Masonry的問題,就索性用它來實現吧=。=)。
效果圖
Github地址
https://github.com/zekunyan/AutolayoutExampleWithMasonry
關於例子工程結構
實現的時候采用的是用StoryBoard拖拽約束+Masonry手寫代碼相結合的方式實現。最關鍵的地方是用Masonry,為了更好地突出重點。其它的無關緊要的空間約束,直接就拖拽了。
關於Autolayout
剛開始學習Autolayout的時候,什麼“Leading Edges”、“Horizontal Centers”,好多啊,感覺一下子適應不來,有時候面對一個界面布局上的需求,可能都無從下手。
總的來說,我覺得Autolayout的關鍵就是“Constraint(約束)”。其實就是一下兩點:
從顯式設置frame的屬性,到利用約束控制View的大小、位置。
思考如何布局時,重點從單個的View,到整體所有View之間的相互關系。
既然沒有了具體設置View的frame屬性,也就是說,系統會在運行時,通過我們設定的“約束”,計算出每個View的frame,再去繪制屏幕內容。
也就是說,我們設置的Constraint,要能體現出View的位置(x、y坐標)、大小(寬高)。無論是用IB拖拽約束,還是手寫代碼,只要從這個角度去思考,很多問題就都能解決。
有關Autolayout的知識,網上有很多,在這裡就不詳細列出了,但是有個公式倒是可以貼出來:
viewA-attribute = viewB-attribute * multiplier + constant
關於Masonry
好用!
Case 1: 並排兩個label,寬度由內容決定。父級View寬度不夠時,優先顯示左邊label的內容
遇到這種跟內容壓縮、優先級有關的布局,就不得不提Autolayout中的兩個重要的屬性“Content Compression Resistance”和“Content Hugging”。
Content Compression Resistance = 不許擠我!
對,這個屬性說白了就是“不許擠我”=。=
這個屬性的優先級(Priority)越高,越不“容易”被壓縮。也就是說,當整體的空間裝不小所有的View的時候,Content Compression Resistance優先級越高的,現實的內容越完整。
Content Hugging = 抱緊!
這個屬性的優先級越高,整個View就要越“抱緊”View裡面的內容。也就是View的大小不會隨著父級View的擴大而擴大。
分析
根據要求,可以將約束分為兩個部分:
整體空間足夠時,兩個label的寬度由內容決定,也就是說,label的“Content Hugging”優先級很高,而且沒有固定的Width屬性。
整體空間不夠時,左邊的label更不容易被壓縮,也就是“Content Compression Resistance”優先級更高。
重點:
label不設置具體的寬度(width)屬性,寬度由內容決定。
顯示的優先級由“Content Compression Resistance”屬性的高低決定。
約束示例圖
關鍵代碼
關鍵的代碼如下:(label1是左邊的label,label2是右邊的)
設置位置
// label1: 位於左上角 [_label1 mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(_contentView1.mas_top).with.offset(5); make.left.equalTo(_contentView1.mas_left).with.offset(2); // 40高度 make.height.equalTo(@40); }]; // label2: 位於右上角 [_label2 mas_makeConstraints:^(MASConstraintMaker *make) { //左邊貼著label1,間隔2 make.left.equalTo(_label1.mas_right).with.offset(2); //上邊貼著父view,間隔5 make.top.equalTo(_contentView1.mas_top).with.offset(5); //右邊的間隔保持大於等於2,注意是lessThanOrEqual //這裡的“lessThanOrEqualTo”放在從左往右的X軸上考慮會更好理解。 //即:label2的右邊界的X坐標值“小於等於”containView的右邊界的X坐標值。 make.right.lessThanOrEqualTo(_contentView1.mas_right).with.offset(-2); //只設置高度40 make.height.equalTo(@40); }];
設置內容約束
//設置label1的content hugging 為1000 [_label1 setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; //設置label1的content compression 為1000 [_label1 setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; //設置右邊的label2的content hugging 為1000 [_label2 setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; //設置右邊的label2的content compression 為250 [_label2 setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
小節
靈活運用“Content Compression Resistance”和“Content Hugging”屬性。
Case 2: 四個ImageView整體居中,可以任意顯示、隱藏
先看看示例的截圖:
下面的四個Switch控件分別控制上面對應位置的圖片是否顯示。
分析
首先就是整體居中,為了實現這個,最簡單的辦法就是將四個圖片“裝進”一個容器View裡面,然後讓這個容器View在整個頁面中居中即可。這樣就不用控制每個圖片的居中效果了。
然後就是顯示與隱藏。在這裡我直接控制圖片ImageView的寬度,寬度為0的時候不就“隱藏”了嗎。
約束示例圖
解釋
之所以這麼設置,主要目的有以下幾點:
盡量減少無效的約束,保證約束不多也不少。
內部的每個imageView約束其實都只有四個:left、centerY、width和height,這樣有個好處,就是可以寫個函數,專門為View一次性添加這幾個約束,大大減少代碼量。
最右邊的imageView還要單獨設置跟容器View的右邊約束,是為了不用設置容器View的width,保證容器View是剛好包含內部的View的,這樣整體才是居中的。
關鍵代碼
先看看設置每個imageView約束的函數:
/** * 設置view的寬高、左邊約束,垂直中心約束 * * @param view 要設置的view * @param size CGSize * @param left 左邊對齊的約束 * @param centerY 垂直中心對齊的約束 * * @return 返回寬約束,用於顯示、隱藏單個view */ - (MASConstraint *)setView:(UIView *)view size:(CGSize)size left:(MASViewAttribute *)left centerY:(MASViewAttribute *)centerY { __block MASConstraint *widthConstraint; [view mas_makeConstraints:^(MASConstraintMaker *make) { //寬高固定 widthConstraint = make.width.equalTo(@(size.width)); make.height.equalTo(@(size.height)); //左邊約束 make.left.equalTo(left); //垂直中心對齊 make.centerY.equalTo(centerY); }]; return widthConstraint; }
接著就是設置容器View的代碼:
//containerView 就是 容器View [_containerView mas_makeConstraints:^(MASConstraintMaker *make) { //只設置高度,寬度由子View決定 make.height.equalTo(@(IMAGE_SIZE)); //水平居中 make.centerX.equalTo(self.view.mas_centerX); //距離父View頂部200點 make.top.equalTo(self.view.mas_top).offset(200); }];
最後是內部imageView的約束:
//分別設置每個imageView的寬高、左邊、垂直中心約束,注意約束的對象 //每個View的左邊約束和左邊的View的右邊相等=。=,有點繞口... UIImageView *imageView1 = _imageViews[0]; MASConstraint *width = [self setView:imageView1 size:imageViewSize left:_containerView.mas_left centerY:_containerView.mas_centerY]; [_widthConstraints addObject:width]; UIImageView *imageView2 = _imageViews[1]; width = [self setView:imageView2 size:imageViewSize left:imageView1.mas_right centerY:_containerView.mas_centerY]; [_widthConstraints addObject:width]; UIImageView *imageView3 = _imageViews[2]; width = [self setView:imageView3 size:imageViewSize left:imageView2.mas_right centerY:_containerView.mas_centerY]; [_widthConstraints addObject:width]; UIImageView *imageView4 = _imageViews[3]; width = [self setView:imageView4 size:imageViewSize left:imageView3.mas_right centerY:_containerView.mas_centerY]; [_widthConstraints addObject:width]; //最後設置最右邊的imageView的右邊與父view的最有對齊 [imageView4 mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(_containerView.mas_right); }];
控制ImageView顯示、隱藏的時候,直接讓其寬度等於0就行:
- (IBAction)showOrHideImage:(UISwitch *)sender { NSUInteger index = (NSUInteger) sender.tag; MASConstraint *width = _widthConstraints[index]; if (sender.on) { width.equalTo(@(IMAGE_SIZE)); } else { width.equalTo(@0); } }
小節
有時候用個“容器View”管理內部的View,往往會起到事半功倍的效果。而且在組織約束的時候,盡量的將約束統一起來,這樣可以用一個函數去設置,減少代碼量。
Case 3: 子View的寬度始終是父級View的一半(或者任意百分比)
其實這個很簡單=。= 再看看這個公式:
viewA-attribute = viewB-attribute * multiplier + constant
這個是Autolayout裡面一個約束的不同屬性的基本組合關系,替換成寬度的話,就是下面這樣:
子View的寬度 = 父級View寬度 * 系數 + 常數;
在Masonry裡面,其實有個函數“multipliedBy”,就是用來設置multipler屬性的(跟原本的NSLayoutConstraint的對應)。
關鍵代碼
如下:
[subView mas_makeConstraints:^(MASConstraintMaker *make) { //上下左貼邊 make.left.equalTo(_containerView.mas_left); make.top.equalTo(_containerView.mas_top); make.bottom.equalTo(_containerView.mas_bottom); //寬度為父view的寬度的一半 make.width.equalTo(_containerView.mas_width).multipliedBy(0.5); }];
接著,只要控制父級View的寬度,子View的寬度就會隨著變化了。
小節
multipliedBy在Masonry的Github主頁裡面沒有=。=
所以要養成讀頭文件的習慣~
總結
有關Autolayout的東西還有好多沒有寫,什麼動畫啊、動態修改約束之類的,本文也算是個引子吧,任重而道遠~
能看到這的朋友,也算是很有耐心了,哈哈~~
參考
SnapKit/Masonry
Masonry介紹與使用實踐(快速上手Autolayout)
AutoLayout:忘掉Frame,擁抱Constraint
Autolayout 基礎
對Auto Layout中的Content Compression Resistance和Content Hugging的總結
Auto Layout Guide