iOS自动布局笔记

1 autoresizing

1
autoresizingMask:创建视图的同时给出其相对于父视图的“对齐方式与缩放系数”。当父视图发生变化时,通过每个子视图的autoresizingMask即可自动得出新的位置,而无需开发者提供。

缺点:

  • 其描述界面变化规则不够灵活,很多变化规则根本无法精确描述。autoresizingMask缩放比例是UIKit内部计算的,开发者无法指定缩放比例的精确值。

  • 变化规则只能基于父视图与子视图之间,无法建立同级视图或者跨级视图之间的关系。

样例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)viewDidLoad {
[super viewDidLoad];

UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 200)];
containerView.backgroundColor = [UIColor blueColor];
[self.view addSubview:containerView];

UILabel *text = [[UILabel alloc] initWithFrame:CGRectZero];
text.text = @"1231312";
[containerView addSubview:text];
text.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth;
text.frame = CGRectMake(0, 0, containerView.bounds.size.width - 20, 100);

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
containerView.frame = CGRectMake(0, 0, 300, 200);
NSLog(@"%@ %@ %@ %@", @(text.frame.origin.x), @(text.frame.origin.y), @(text.frame.size.width),
@(text.frame.size.height));
});
}

2 autolayout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
autolayout:它允许开发者在界面上的任意两个视图之间建立精确的线性变化规则。所谓线性变化就是数学中的一次函数,即:y = m*x + c,其中x和y是界面中任意两个视图的某个布局属性,m为比例系数,c为常量。每个线性变化规则称之为布局约束(Layout Constraint)。
```

### 2.1 NSLayoutConstraint

```objc
NS_CLASS_AVAILABLE_IOS(6_0)
@interface NSLayoutConstraint : NSObject
...
@property (readonly, assign) id firstItem;
@property (readonly) NSLayoutAttribute firstAttribute;
@property (readonly) NSLayoutRelation relation;
@property (readonly, assign) id secondItem;
@property (readonly) NSLayoutAttribute secondAttribute;
@property (readonly) CGFloat multiplier;
@property CGFloat constant;
...
+(instancetype)constraintWithItem:(id)firstItem attribute:(NSLayoutAttribute)firstAttribute
relatedBy:(NSLayoutRelation)relation
toItem:(id)secondItem attribute:(NSLayoutAttribute)secondAttribute
multiplier:(CGFloat)multiplier constant:(CGFloat)constant;

公式:firstItem.firstAttribute {==,<=,>=} secondItem.secondAttribute * multiplier + constant

解释:firstItem与secondItem分别是界面中受约束的视图与被参照的视图。他们不一定非得是兄弟关系或者父子关系,只要是他们 有着共同的祖先视图 即可,这一点是autoresizingMask无法做到的。
firstAttribute与secondAttribute分别是firstItem与secondItem的某个布局属性(NSLayoutAttribute)。注意,firstItem与secondItem不一定非得是同样的值, 允许定义诸如某视图的高度等于另一个视图的宽度这样的约束。NSLayoutAttributeNotAnAttribute这个额外解释一下,当我们需要为某个视图精确指定一个宽度或者高度值时,这时候secondItem为nil,secondAttribute为NSLayoutAttributeNotAnAttribute。relation定义了布局关系(NSLayoutRelation)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

UIView *v1 = [UIView new];
v1.backgroundColor = [UIColor blueColor];
v1.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:v1];


NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:v1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1
constant:0];
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:v1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1
constant:0];
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:v1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1
constant:0];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:v1
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
// iOS8 以下
// [v1 addConstraint:topConstraint];
// [v1 addConstraint:leftConstraint];
// [v1 addConstraint:rightConstraint];
// [v1 addConstraint:heightConstraint];
// iOS8及以上
topConstraint.active = YES;
leftConstraint.active = YES;
rightConstraint.active = YES;
heightConstraint.active = YES;
}

2.2 VFL

iOS自动布局形式化描述语言。

2.3 自身内容尺寸约束、修改约束、布局动画

1
自身内容尺寸约束:一般来说,要确定一个视图的精确位置,至少需要4个布局约束(以确定水平位置x、垂直位置y、宽度w和高度h)。但是,某些用来展现内容的用户控件,例如文本控件UILabel、按钮UIButton、图片视图UIImageView等,它们具有自身内容尺寸(Intrinsic Content Size),此类用户控件会根据自身内容尺寸添加布局约束。也就是说,如果开发者没有显式给出其宽度或者高度约束,则其自动添加的自身内容约束将会起作用。因此看似“缺失”约束,实际上并非如此。

对于约束的如下几个重要属性:

1
2
3
4
5
6
7
8
9
10
@property (readonly, assign) id firstItem;
@property (readonly) NSLayoutAttribute firstAttribute;
@property (readonly) NSLayoutRelation relation;
@property (readonly, assign) id secondItem;
@property (readonly) NSLayoutAttribute secondAttribute;
@property (readonly) CGFloat multiplier;

/* Unlike the other properties, the constant may be modified after constraint creation. Setting the constant on an existing constraint performs much better than removing the constraint and adding a new one that's just like the old but for having a new constant.
*/
@property CGFloat constant;

更新约束

当使用代码来修改约束时,只能修改约束的常量值constant。一旦创建了约束,其他只读属性都是无法修改的,特别要注意的是比例系数multiplier也是只读的。

添加删除约束

1
2
3
4
5
6
7
8
9
10
11
- (void)keyboardWillShow:(NSNotification *)notification
{
self.labelCenterYNormalCons.active = NO;
self.labelCenterYKeyboardCons.active = YES;
}

- (void)keyboardWillHide:(NSNotification *)notification
{
self.labelCenterYKeyboardCons.active = NO;
self.labelCenterYNormalCons.active = YES;
}

尽量先设置需要将active置为NO的约束,然后再设置需要将active置为YES的约束,如果颠倒上面两条语句的话,可能会引起运行时约束错误。

修改约束优先级

1
2
3
4
5
6
7
8
9
10
11
- (void)keyboardWillShow:(NSNotification *)notification
{
self.labelCenterYNormalCons.priority = UILayoutPriorityDefaultLow;
self.labelCenterYKeyboardCons.priority = UILayoutPriorityDefaultHigh;
}

- (void)keyboardWillHide:(NSNotification *)notification
{
self.labelCenterYKeyboardCons.priority = UILayoutPriorityDefaultLow;
self.labelCenterYNormalCons.priority = UILayoutPriorityDefaultHigh;
}

需要注意的是,只能修改可选约束的优先级,也就是说:

- 不允许将优先级由小于1000的值改为1000
- 不允许将优先级由1000修改为小于1000的值

例如,如果将优先级由250修改为1000,则会抛出异常。

自身内容尺寸约束的抗挤压与抗拉抻效果

弹簧会有自身固有长度,当有外力作用时,弹簧会抵抗外力作用,尽量接近固有长度。

1
2
抗拉抻:当外力拉长弹簧时,弹簧长度大于固有长度,且产生向内收的力阻止外力拉抻,且尽量维持长度接近自身固有长度。 
抗挤压:当外力挤压弹簧时,弹簧长度小于固有长度,且产生向外顶的力阻止外力挤压,且尽量维持长度接近自身固有长度。

对于自身内容尺寸约束,Hug值表示抗拉抻优先级,CompressionResistance值表示抗压缩优先级。Hug值越高越难被拉抻,CompressionResistance值越高越难被压缩。这两个都是针对自身内容尺寸。这两个值越高,在自动布局的时候,view的真实布局就越接近自身内容尺寸。他们表达的是一种自身内容尺寸约束对外加约束的抵抗力。

参考文档:

  1. iOS 8 Auto Layout界面自动布局系列1-自动布局的基本原理
  2. iOS 8 Auto Layout界面自动布局系列2-使用Xcode的Interface Builder添加布局约束
  3. iOS 8 Auto Layout界面自动布局系列3-使用代码添加布局约束
  4. iOS 8 Auto Layout界面自动布局系列4-使用VFL添加布局约束
  5. iOS 8 Auto Layout界面自动布局系列5-自身内容尺寸约束、修改约束、布局动画