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》第十六章的总结。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容