相對于其他的自動布局語言來說VFL代碼量減少了很多,就比如要實作下面這個限制:
布局代碼:
UIView *view = [UIView new];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
//傳統的布局方式
CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);
// 使用 Auto Layout 布局
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
// `view` 的左邊距離 `self.view` 的左邊 50 點.
NSLayoutConstraint *viewLeft = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1
constant:CGRectGetMinX(viewFrame)];
// `view` 的頂部距離 `self.view` 的頂部 100 點.
NSLayoutConstraint *viewTop = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:CGRectGetMinY(viewFrame)];
// `view` 的寬度 是 60 點.
NSLayoutConstraint *viewWidth = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:CGRectGetWidth(viewFrame)];
// `view` 的高度是 60 點.
NSLayoutConstraint *viewHeight = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:CGRectGetHeight(viewFrame)];
// 把限制添加到父視圖上.
[self.view addConstraints:@[viewLeft, viewTop, viewWidth, viewHeight]];
如果使用VFL語言隻需要下面這幾句
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
最關鍵的是這2句
H:|-50-[view(>=150)]
V:|-100-[view(>=150)]
第一句是在水準方向布局,表示 view 左邊距離父視圖左邊 50 點,寬度至少 150 點。(水準方向是寬度)
第二句是在垂直方向上布局,表示 view 頂部距離父視圖頂部 100 點,寬度至少 150 點。(垂直方向是高度)
分解說明如下:
H / V 表示布局方向。H 表示水準方向(Horizontal),V 表示垂直方向(Vertical),方向後要緊跟一個 :,不能有空格。
| 表示父視圖。通常出現在語句的首尾。
- 有兩個用途,單獨一個表示标準距離。這個值通常是 8 ;兩個中間夾着數值,表示使用中間的數值代替标準距離,如第一句的 -50-,就是使用 50 來代替标準距離。
[] 表示對象,括号中間需要填上對象名,對象名必須是我們傳入的 views 字典中的鍵。對象名後可以跟小括号 (),小括号中是對此對象的尺寸和優先級限制。水準布局中尺寸是寬度,垂直布局中尺寸是高度。如第一句中的 (>=150) 就是對 view 尺寸的限制,因為是水準方向布局,是以它表示寬度大于或等于 150 點。而 150 前面的 >= 就是我們上面第一個方法中提到的關系參數。括号中可以包含多條限制,如果我們想再加一條限制,保證 view 的寬度最大不超過 200 點,我們可以這樣寫:H:|-50-[view(>=150,<=200)]。
VFL 文法有幾點需要注意:
布局語句中不能包含空格
和關系一樣,沒有 >、< 這種限制
然後下面是一些例子,增加你對 VFL 文法的了解。
例一:
我們在 view 右側添加另一個視圖 view2,效果如圖:
實作代碼:
UIView *view = [UIView new];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
UIView *view2 = [UIView new];
[view2 setBackgroundColor:[UIColor blueColor]];
[self.view addSubview:view2];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[view2 setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view]-[view2(>=50)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view2(>=50)]" options:0 metrics:nil views:views]];
我們講講最後的兩條新的 VFL 語句:
H:[view]-[view2(>=50)]
從開始的 H: 我們可以判斷出這是水準方向的布局,換句話說就是設定視圖的 x 和 width。接着的 [view],說明後面的所有視圖都是在 view 的右側;接着是 -,說明後一個視圖和 view 之間有一個标準距離的間距;也就是說 x 等于 view 的右側再加上标準距離,即 CGRectGetMaxX(view) + 标準距離。最後是 [view2(>=50)],這裡可以看出後一個視圖是 view2,并且它的寬度不小于 50 點。整一句翻譯成白話就是說:在水準方向上,view2 在 view 右側的标準距離位置處,并且它的寬度不小于 50 點。
V:|-100-[view2(>=50)]
從開始的 V: 我們可以判斷出這是垂直方向的布局,換句話說就是設定視圖的 y 和 height。接着的 | 說明是後一個視圖是相對于父視圖進行布局;接着是 -100-,說明垂直方向和父視圖(頂部)相距 100 點,也就是說 y 等于 100 點。最後是 [view2(>=50)],這和上一句相同,隻是因為是垂直方向,是以 50 是設定高度而不是寬度。整一句翻譯成白話就是說:在垂直方向上,view2 在相對于父視圖(頂部) 100 點的位置處,并且它的高度不小于 50 點。
下面我們說一下各個參數的作用和意義
options
這個參數的值是位掩碼,使用頻率并不高,但非常有用。它可以操作在 VFL 語句中的所有對象的某一個屬性或方向。例如上面的例一,水準方向有兩個視圖,它們的垂直方向到頂部的距離相同,或者說頂部對齊,我們就可以給這個參數傳入 NSLayoutFormatAlignAllTop 讓它們頂部對齊,這樣以來隻需要指定兩個視圖的其中一個的垂直方向到頂部的距離就可以了。代碼:
...
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" options:NSLayoutFormatAlignAllTop metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[view2(>=50)]" options:0 metrics:nil views:views]];
它的預設值是 NSLayoutFormatDirectionLeadingToTrailing,根據目前使用者的語言環境進行設定,比如英文中就是從左到右,希伯來語中就是從右到左。
這個值符合我們常用的選項。NSLayoutFormatDirectionLeadingToTrailing 的值是 0 << 16,是以我們可以直接傳入 0 使用此值。
因為是位掩碼,是以我們可以使用 | 進行多選
metrics
這是一個字典,字典的鍵必須是出現在 VFL 語句中的字元串,值必須是 NSNumber 類型,作用是将在 VFL 語句中出現的鍵替換為相應的值。例如本文中的第一個布局的例子,使用了這個參數後代碼就變成了這樣:
UIView *view = [UIView new];
[view setBackgroundColor:[UIColor redColor]];
[self.view addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);
NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);
NSDictionary *metrics = @{@"left": @(CGRectGetMinX(viewFrame)),
@"top": @(CGRectGetMinY(viewFrame)),
@"width": @(CGRectGetWidth(viewFrame)),
@"height": @(CGRectGetHeight(viewFrame))};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view(>=height)]" options:0 metrics:metrics views:views]];
聰明的你看了這段代碼後肯定已經明白這個參數的用途了,雖然使用頻率不高,但依然很有用,特别是要動态計算限制值的時候非常有用。
實際上這個參數也可以使用 NSDictionaryOfVariableBindings(...) 宏來快速建立
views
又是一個字典,包含了 VFL 語句中用到的視圖。字典的鍵必須是出現在 VFL 語句中的視圖名稱,值必須視圖的執行個體。這個字典我們在講 format 時已經講過,也用過很多次,相信你早已明白是怎麼回事了。
講了這麼多,可能你也發現了,隻要學會了 VFL 文法,就可以友善地使用 Auto Layout 了,其他的知識都屬于輔助選項,會的話,布局更輕松一些,不會也沒關系,實踐多了,自然就會了。
優先級 (Priority level)
限制條件有優先級,高優先級限制會比低優先級限制優先得到滿足,系統内置了 4 個優先級:
enum {
UILayoutPriorityRequired = 1000,
UILayoutPriorityDefaultHigh = 750,
UILayoutPriorityDefaultLow = 250,
UILayoutPriorityFittingSizeLevel = 50,
};
typedef float UILayoutPriority;
UILayoutPriorityRequired 這是預設值,這意味着這個限制條件必須被精确地滿足。
UILayoutPriorityDefaultHigh
UILayoutPriorityDefaultLow
UILayoutPriorityFittingSizeLevel 這是内置的最低優先級。
相信你已經看到每個等級的數值了,優先級的取值在 0 ~ 1000 之間,取值越大,優先級越高,越會被優先滿足。
每個限制的預設優先級就是 UILayoutPriorityRequired,這意味着你給出的所有限制都必須得到滿足,一旦限制間發生沖突,你的應用就會 Crash。這也是在使用 Auto Layout 時經常會犯的錯誤:沒有給限制設定适當的優先級。
最後 補充一點複雜一些的用法
// 第一個視圖指定的 attribute 是第二個視圖給的 attribute*multiplier+constant
//relatedBy 是指大于等于 等于 小于等于中的一種 [self.view addConstraint:[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:view3 attribute:NSLayoutAttributeHeight multiplier:2 constant:0]];