Auto Layout: Programmatic Constraints
Apple recommends that you create and constrain your views in a XIB file whenever possible. However, if your views are created in code, then you will need to constrain them programmatically.
蘋果建議通過XIB文件創(chuàng)建視圖以及為視圖添加約束,然而如果視圖是通過代碼方式創(chuàng)建的,則要通過代碼方式添加約束。
如果要通過代碼方式創(chuàng)建和約束完整的view hierarchy,則重寫loadView方法。如果需要一個(gè)單獨(dú)的view,然后將其添加到通過XIB創(chuàng)建的view hierarchy中,則重寫viewDidLoad方法來創(chuàng)建及添加約束。
本章我們通過代碼方式重新創(chuàng)建XIB中的image view,然后將其添加到XIB生成的view hierarchy,所以重寫viewDidLoad方法:
- (void)viewDidLoad{
[super viewDidLoad];
UIImageView *iv = [[UIImageView alloc] initWithImage:nil];
iv.contentMode = UIViewContentModeScaleAspectFit;
iv.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:iv];
self.imageView = iv;
}
autoresizing masks
在引入Auto Layout之前,iOS應(yīng)用使用autoresizing masks去讓view在不同大小屏幕上縮放。
每個(gè)view都有autoresizing mask,默認(rèn)情況下,iOS創(chuàng)建匹配autoresizing mask的約束并添加到view上,這些translated constraints經(jīng)常和明確指定的約束沖突,導(dǎo)致unsatisfiable constraints。
為了規(guī)避這種問題,將translatesAutoresizingMaskIntoConstraints屬性設(shè)置為NO,來關(guān)閉translated constraints。
VFL - Visual Format Language
如果以代碼方式添加約束,蘋果推薦使用VFL。VFL通過字符串來描述約束,一個(gè)VFL字符串可以描述多個(gè)約束,但是一個(gè)VFL不能同時(shí)描述水平和垂直方向的約束。
對(duì)于image view,需要兩個(gè)VFL字符串來分別描述水平和垂直方向的約束。
- 水平方向的約束:
@"H:|-0-[imageView]-0-|"
H:代表水平方向的約束,[view]來標(biāo)識(shí)視圖,|代表view's container,這個(gè)VFL約束的意思是:image view左側(cè)方向距其容器0 point,右側(cè)方向距其容器0 point。
當(dāng)是0 point時(shí),-0-可以省略,所以上面的VFL等價(jià)與:
@"H:|[imageView]|"
- 垂直方向的約束
@"V:[dateLabel]-8-[imageView]-8-[toolbar]"
V:代表垂直方向,image view top方向距date label 8 points,bottom方向距toolbar 8 points。
視圖間的標(biāo)準(zhǔn)間距是8 points,-默認(rèn)設(shè)置間距為8 points,所以上面的VFL等價(jià)與:
@"V:[dateLabel]-[imageView]-[toolbar]"
如果有兩個(gè)image view在水平方向排列,間距是10 points,左側(cè)image view距容器左側(cè)20 points,右側(cè)image view距容器右側(cè)20 points,則其VFL為:
@"H:|-20-[imageViewLeft]-10-[imageViewRight]-20-|"
設(shè)置視圖高度為50 points:
@"V:[someView(==50)]"
Creating Constraints
constraint是NSLayoutConstraint的實(shí)例,創(chuàng)建約束調(diào)用類方法:
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
該方法返回一個(gè)NSLayoutConstraint實(shí)例數(shù)組,因?yàn)橐粋€(gè)VFL可以描述多個(gè)約束。
第一個(gè)參數(shù)是VFL,第四個(gè)參數(shù)是字典,其中字義了VFL中使用的視圖名稱對(duì)應(yīng)的視圖對(duì)象。
定義視圖名稱的KEY,可以是任意字符,但是不能是|,這個(gè)是保留字符,代表view's container。
- (void)viewDidLoad{
[super viewDidLoad];
UIImageView *iv = [[UIImageView alloc] initWithImage:nil];
iv.contentMode = UIViewContentModeScaleAspectFit;
iv.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:iv];
self.imageView = iv;
// 創(chuàng)建水平和垂直方向約束
NSDictionary *nameMap = @{@"imageView": self.imageView,
@"dateLabel": self.dateLabel,
@"toolbar": self.toolbar};
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[imageView]-0-|" options:0 metrics:nil views:nameMap];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[dateLabel]-[imageView]-[toolbar]" options:0 metrics:nil views:nameMap];
}
Adding Constraints
現(xiàn)在已經(jīng)創(chuàng)建了兩個(gè)NSLayoutConstraint對(duì)象的數(shù)組,但是還需要調(diào)用UIView實(shí)例的如下方法將約束添加給view:
- (void)addConstraints:(NSArray *)constraints
應(yīng)該哪個(gè)view來調(diào)用這個(gè)方法呢?以下規(guī)則來確定在哪個(gè)view上添加約束:
- 如果一個(gè)約束影響兩個(gè)view,而且這兩個(gè)view有相同的superview,則將約束添加他們的superview上(圖中約束A)。
- 如果一個(gè)約束只影響一個(gè)view,則將約束添加到此view上(圖中約束B)。
- 如果一個(gè)約束影響兩個(gè)view,但是這兩個(gè)view的superview不相同,不過他們有共同的祖先view,則將約束添加到祖先view上(圖中約束C)。
- 如果一個(gè)約束影響一個(gè)view以及其superview,則將約束添加到superview(圖中約束D)。
Intrinsic Content Size
固有內(nèi)容大小,image view的固有內(nèi)容大小是其中的圖片大小。Auto Layout根據(jù)view的intrinsic content size會(huì)創(chuàng)建一些約束,這類約束有兩個(gè)優(yōu)先級(jí):Content hugging priority和Content compression resistance priority。
| Priority | Description |
|---|---|
| Content hugging | view到其intrinsic content的距離是否可以增大的優(yōu)先級(jí),如果值是1000,表示view不能大于其intrinsic content的大小;當(dāng)值小于1000,view可以大于其內(nèi)容大小。此優(yōu)先級(jí)越小,越可能變大 |
| Content compression resistance | 避免view收縮的優(yōu)先級(jí),如果值是1000,表示view不能小于其內(nèi)容大??;當(dāng)值小于1000,view可以自由收縮。此優(yōu)先級(jí)越小,越可能收縮 |
這兩個(gè)屬性還可具體到水平和垂直方向,在Interface Builder中可查看這些priority。
我們可以看到value text field的Content hugging vertical priority是250,而image view的Content hugging vertical priority是251,text field的更小,所以Auto Layout選擇讓value text field變得更大,就導(dǎo)致了下圖的效果。
要解決此問題,將image view的Content hugging vertical priority改為200,讓Auto Layout選擇讓image view來變得更大。
[self.imageView setContentHuggingPriority:200 forAxis:UILayoutConstraintAxisVertical];
其他方式添加約束
如果約束不能通過VFL創(chuàng)建,比如,不能通過VFL創(chuàng)建比例類約束,例如約束一個(gè)image view的寬度是其高度的1.5倍。此時(shí)可以通過NSLayoutConstraint的另外一個(gè)方法來添加。
+ (id)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
創(chuàng)建約束:view1.attr1 = view2.attr2 * multiplier + constant,如果沒有view2和attr2,用nil和NSLayoutAttributeNotAnAttribute代替。
attribute可以是以下常量值:
- NSLayoutAttributeLeft
- NSLayoutAttributeRight
- NSLayoutAttributeTop
- NSLayoutAttributeBottom
- NSLayoutAttributeWidth
- NSLayoutAttributeHeight
- NSLayoutAttributeBaseline
- NSLayoutAttributeCenterX
- NSLayoutAttributeCenterY
- NSLayoutAttributeLeading
- NSLayoutAttributeTrailing
為image view添加約束,其寬度是高度的1.5倍
NSLayoutConstraint * aspectConstraint =
[NSLayoutConstraint constraintWithItem:self.imageView
attribute:NSLayoutAttributeWidth
toItem:self.imageView
attribute:NSLayoutAttributeHeight
multiplier:1.5
constant:0.0];
將約束添加到視圖,執(zhí)行view的以下方法,到底是哪個(gè)view執(zhí)行此方法,和之前VFL確定view的規(guī)則一致。
- (void)addConstraint:(NSLayoutConstraint *)constraint
此處該約束只影響image view,所以由image view來調(diào)用添加約束的方法:
[self.imageView addConstraint:aspectConstraint];
本文是對(duì)《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十六章的總結(jié)。