Auto Layout: Programmatic Constraints

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上添加约束:

  1. 如果一个约束影响两个view,而且这两个view有相同的superview,则将约束添加他们的superview上(图中约束A)。
  2. 如果一个约束只影响一个view,则将约束添加到此view上(图中约束B)。
  3. 如果一个约束影响两个view,但是这两个view的superview不相同,不过他们有共同的祖先view,则将约束添加到祖先view上(图中约束C)。
  4. 如果一个约束影响一个view以及其superview,则将约束添加到superview(图中约束D)。

Intrinsic Content Size

http://stackoverflow.com/questions/15850417/cocoa-autolayout-content-hugging-vs-content-compression-resistance-priority

固有内容大小,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》第十六章的总结。

你可能感兴趣的:(Auto Layout: Programmatic Constraints)