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文件创建视图以及为视图添加约束,然而如果视图是通过代码方式创建的,则要通过代码方式添加约束。
如果要通过代码方式创建和约束完整的view hierarchy,则重写loadView方法。如果需要一个单独的view,然后将其添加到通过XIB创建的view hierarchy中,则重写viewDidLoad方法来创建及添加约束。
本章我们通过代码方式重新创建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应用使用autoresizing masks去让view在不同大小屏幕上缩放。
每个view都有autoresizing mask,默认情况下,iOS创建匹配autoresizing mask的约束并添加到view上,这些translated constraints经常和明确指定的约束冲突,导致unsatisfiable constraints。
为了规避这种问题,将translatesAutoresizingMaskIntoConstraints属性设置为NO,来关闭translated constraints。
VFL - Visual Format Language
如果以代码方式添加约束,苹果推荐使用VFL。VFL通过字符串来描述约束,一个VFL字符串可以描述多个约束,但是一个VFL不能同时描述水平和垂直方向的约束。
对于image view,需要两个VFL字符串来分别描述水平和垂直方向的约束。
- 水平方向的约束:
@"H:|-0-[imageView]-0-|"
H:
代表水平方向的约束,[view]
来标识视图,|
代表view's container,这个VFL约束的意思是:image view左侧方向距其容器0 point,右侧方向距其容器0 point。
当是0 point时,-0-
可以省略,所以上面的VFL等价与:
@"H:|[imageView]|"
- 垂直方向的约束
@"V:[dateLabel]-8-[imageView]-8-[toolbar]"
V:
代表垂直方向,image view top方向距date label 8 points,bottom方向距toolbar 8 points。
视图间的标准间距是8 points,-
默认设置间距为8 points,所以上面的VFL等价与:
@"V:[dateLabel]-[imageView]-[toolbar]"
如果有两个image view在水平方向排列,间距是10 points,左侧image view距容器左侧20 points,右侧image view距容器右侧20 points,则其VFL为:
@"H:|-20-[imageViewLeft]-10-[imageViewRight]-20-|"
设置视图高度为50 points:
@"V:[someView(==50)]"
Creating Constraints
constraint是NSLayoutConstraint的实例,创建约束调用类方法:
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
该方法返回一个NSLayoutConstraint实例数组,因为一个VFL可以描述多个约束。
第一个参数是VFL,第四个参数是字典,其中字义了VFL中使用的视图名称对应的视图对象。
定义视图名称的KEY,可以是任意字符,但是不能是|
,这个是保留字符,代表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;
// 创建水平和垂直方向约束
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
现在已经创建了两个NSLayoutConstraint对象的数组,但是还需要调用UIView实例的如下方法将约束添加给view:
- (void)addConstraints:(NSArray *)constraints
应该哪个view来调用这个方法呢?以下规则来确定在哪个view上添加约束:
- 如果一个约束影响两个view,而且这两个view有相同的superview,则将约束添加他们的superview上(图中约束A)。
- 如果一个约束只影响一个view,则将约束添加到此view上(图中约束B)。
- 如果一个约束影响两个view,但是这两个view的superview不相同,不过他们有共同的祖先view,则将约束添加到祖先view上(图中约束C)。
- 如果一个约束影响一个view以及其superview,则将约束添加到superview(图中约束D)。
Intrinsic Content Size
固有内容大小,image view的固有内容大小是其中的图片大小。Auto Layout根据view的intrinsic content size会创建一些约束,这类约束有两个优先级:Content hugging priority和Content compression resistance priority。
Priority | Description |
---|---|
Content hugging | view到其intrinsic content的距离是否可以增大的优先级,如果值是1000,表示view不能大于其intrinsic content的大小;当值小于1000,view可以大于其内容大小。此优先级越小,越可能变大 |
Content compression resistance | 避免view收缩的优先级,如果值是1000,表示view不能小于其内容大小;当值小于1000,view可以自由收缩。此优先级越小,越可能收缩 |
这两个属性还可具体到水平和垂直方向,在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变得更大,就导致了下图的效果。
要解决此问题,将image view的Content hugging vertical priority改为200,让Auto Layout选择让image view来变得更大。
[self.imageView setContentHuggingPriority:200 forAxis:UILayoutConstraintAxisVertical];
其他方式添加约束
如果约束不能通过VFL创建,比如,不能通过VFL创建比例类约束,例如约束一个image view的宽度是其高度的1.5倍。此时可以通过NSLayoutConstraint的另外一个方法来添加。
+ (id)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
创建约束: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];
将约束添加到视图,执行view的以下方法,到底是哪个view执行此方法,和之前VFL确定view的规则一致。
- (void)addConstraint:(NSLayoutConstraint *)constraint
此处该约束只影响image view,所以由image view来调用添加约束的方法:
[self.imageView addConstraint:aspectConstraint];
本文是对《iOS Programming The Big Nerd Ranch Guide 4th Edition》第十六章的总结。