【轉】有趣的Autolayout示例-Masonry實現

原文網址:http://tutuge.me/2015/05/23/autolayout-example-with-masonry/html

很久沒有寫Blog了,這段時間有點忙啊=。=
本文舉了3個比較有「特色」的Autolayout例子,源於微博上好友的提問,感受比較有意思,也比較有表明性,就寫了出來,分享給你們~
至於爲何用Masonry,那是由於它好用啊!(被問到過有關Masonry的問題,就索性用它來實現吧=。=)。ios

效果圖

 

效果圖

 

Github地址

https://github.com/zekunyan/AutolayoutExampleWithMasonrygit

關於例子工程結構

實現的時候採用的是用StoryBoard拖拽約束+Masonry手寫代碼相結合的方式實現。最關鍵的地方是用Masonry,爲了更好地突出重點。其它的可有可無的空間約束,直接就拖拽了。github

關於Autolayout

剛開始學習Autolayout的時候,什麼「Leading Edges」、「Horizontal Centers」,好多啊,感受一會兒適應不來,有時候面對一個界面佈局上的需求,可能都無從下手。app

總的來講,我以爲Autolayout的關鍵就是「Constraint(約束)」。其實就是如下兩點:less

  1. 從顯式設置frame的屬性,到利用約束控制View的大小、位置。
  2. 思考如何佈局時,重點從單個的View,到總體全部View之間的相互關係。

既然沒有了具體設置View的frame屬性,也就是說,系統會在運行時,經過咱們設定的「約束」,計算出每一個View的frame,再去繪製屏幕內容。ide

也就是說,咱們設置的Constraint,要能體現出View的位置(x、y座標)大小(寬高)。不管是用IB拖拽約束,仍是手寫代碼,只要從這個角度去思考,不少問題就都能解決。函數

有關Autolayout的知識,網上有不少,在這裏就不詳細列出了,可是有個公式卻是能夠貼出來:佈局

1
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的擴大而擴大。

分析

根據要求,能夠將約束分爲兩個部分:

  1. 總體空間足夠時,兩個label的寬度由內容決定,也就是說,label的「Content Hugging」優先級很高,並且沒有固定的Width屬性。
  2. 總體空間不夠時,左邊的label更不容易被壓縮,也就是「Content Compression Resistance」優先級更高。

重點:

  1. label不設置具體的寬度(width)屬性,寬度由內容決定。
  2. 顯示的優先級由「Content Compression Resistance」屬性的高低決定。

約束示例圖

 

約束示例圖

 

關鍵代碼

關鍵的代碼以下:(label1是左邊的label,label2是右邊的)

設置位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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);
}];

設置內容約束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//設置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控件分別控制上面對應位置的圖片是否顯示。

分析

  1. 首先就是總體居中,爲了實現這個,最簡單的辦法就是將四個圖片「裝進」一個容器View裏面,而後讓這個容器View在整個頁面中居中便可。這樣就不用控制每一個圖片的居中效果了。
  2. 而後就是顯示與隱藏。在這裏我直接控制圖片ImageView的寬度,寬度爲0的時候不就「隱藏」了嗎。

約束示例圖

 

約束示例圖

 

解釋

之因此這麼設置,主要目的有如下幾點:

  1. 儘可能減小無效的約束,保證約束很少也很多。
  2. 內部的每一個imageView約束其實都只有四個:left、centerY、width和height,這樣有個好處,就是能夠在循環裏面依次添加約束,大大減小代碼量。
  3. 最右邊的imageView還要單獨設置跟容器View的右邊約束,是爲了避免用設置容器View的width,保證容器View是恰好包含內部的View的,這樣總體纔是居中的。

關鍵代碼

先看看最外層容器View的代碼:

1
2
3
4
5
6
7
8
9
10
//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

1
2
3
4
5
6
//循環建立、添加imageView
for (NSUInteger i = 0; i < 4; i++) {
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:_imageNames[i]]];
[_imageViews addObject:imageView];
[_containerView addSubview:imageView];
}

最後是循環對imageView加上約束: 感謝XVXVXXX的PR:-D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//設定大小
CGSize imageViewSize = CGSizeMake(IMAGE_SIZE, IMAGE_SIZE);

//分別設置每一個imageView的寬高、左邊、垂直中心約束,注意約束的對象
//每一個View的左邊約束和左邊的View的右邊相等=。=,有點繞口...

// 保存循環中的臨時結果
__block UIView *lastView = nil;
__block MASConstraint *widthConstraint = nil;

NSUInteger arrayCount = _imageViews.count;
[_imageViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
[view mas_makeConstraints:^(MASConstraintMaker *make) {
//寬高固定
widthConstraint = make.width.equalTo(@(imageViewSize.width));
make.height.equalTo(@(imageViewSize.height));
//左邊約束
make.left.equalTo(lastView ? lastView.mas_right : view.superview.mas_left);
//垂直中心對齊
make.centerY.equalTo(view.superview.mas_centerY);
//設置最右邊的imageView的右邊與父view的最右對齊
if (idx == arrayCount - 1) {
make.right.equalTo(view.superview.mas_right);
}

[_widthConstraints addObject:widthConstraint];
lastView = view;
}];
}];

 

控制ImageView顯示、隱藏的時候,直接讓其寬度等於0就行:

1
2
3
4
5
6
7
8
9
10
- (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的一半(或者任意百分比)

其實這個很簡單=。= 再看看這個公式:

1
viewA-attribute = viewB-attribute * multiplier + constant

這個是Autolayout裏面一個約束的不一樣屬性的基本組合關係,替換成寬度的話,就是下面這樣:

1
View的寬度 = 父級View寬度 * 係數 + 常數;

在Masonry裏面,其實有個函數「multipliedBy」,就是用來設置multipler屬性的(跟本來的NSLayoutConstraint的對應)。

關鍵代碼

以下:

1
2
3
4
5
6
7
8
9
[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的東西還有好多沒有寫,什麼動畫啊、動態修改約束之類的,本文也算是個引子吧,任重而道遠~

能看到這的朋友,也算是頗有耐心了,哈哈~~

參考

相關文章
相關標籤/搜索