视图-②布局

视图-②布局

[toc]

一、Intrinsic Content Size,Content Hugging Priority和Content Compression Resistance Priority

看一下下面的例子,看给出的例子约束是否完整?

1
2
3
4
5
6
7
8
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:15];
label.text = @"Hello";
[self.view addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.mas_left).offset(16);
make.top.equalTo(self.view.mas_top).offset(16);
}];

这里只定义了两个约束,left 和 top,只够计算出frame的originX和orginY,没有width和height。那么是不是属于不完整的约束呢?其实在这里给出的约束已经是完整的了。因为对于UILabel这个控件而言 ,只要通过其font和text系统就可以计算出Label该有的长度和宽度。这里的长度和宽度就是UILabel的intrinsic content size(固有属性)。

Intrinsic Content Size, 通俗来讲,就是控件(UIButton,UILabel,UIImageView)能根据它们的内容(content)计算自己的大小(Size)

开发中用到的一些控件或视图,本身就自带大小,比如UIButton控件,设置完title后就能知道这个UIButton是文字的大小再加上两个固定的button margin。
像这种控件或视图本身就带有的高度、宽度,就叫做intrinsic content size(固定内容尺寸)。

2、浅谈 iOS AutoLayout 中 Label 的抗拉伸和抗压缩

在 Autolayout 优先级的范围是 1 ~ 1000,创建一个约束,默认的优先级是最高的 1000。

Content Hugging Priority:
该优先级表示一个控件抗被拉伸的优先级。优先级越高,越不容易被拉伸(即越容易保持原状),默认是251。

Content Compression Resistance Priority:
该优先级表示一个控件抗压缩的优先级。优先级越高,越不容易被压缩(即越容易保持原状),默认是750。

使用场景:

当一个视图上有多个 intrinsic content size 的子控件,并且子控件可能会超出父视图的区域时,此属性可控制哪些视图被内容被优先压缩,使其不超出父视图区域。

场景举例:

1
2
3
4
5
[[yellowLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.left.equalTo(self.view).offset(100);
make.right.equalTo(self.view).offset(-100);
}];

当yellowLable的宽度最多为screenWidth-200。

则我们想让lable对左右两边的约束性没那么高,可以设置

1
2
3
4
5
[yellowLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.left.equalTo(self.view).offset(100).priority(250);
make.right.equalTo(self.view).offset(-100).priority(250);
}];

给出一个比较常见的需求:

在同一行中显示标题和时间,时间必须显示完全,标题如果太长就截取可显示的部分,剩余的用…表示。

intrinsic content size

目标:我们想让绿色的时间显示全,则应该要压缩前面的titleLabel。也就是要降低titleLabel的抗压缩。

1
2
3
4
5
if (b) {
[timeLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
// 或
//[titleLabel setContentHuggingPriority:UILayoutPriorityFittingSizeLevel forAxis:UILayoutConstraintAxisHorizontal];
}

UILayoutPriorityRequired:1000

UILayoutPriorityDefaultHigh:750

UILayoutPriorityDefaultLow:250

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
//在同一行中显示标题和时间,时间必须显示完全,标题如果太长就截取可显示的部分,剩余的用…表示。
- (UIView *)contentViewWith:(BOOL)b {
UIView *contentView = [[UIView alloc] init];
contentView.backgroundColor = [UIColor lightGrayColor];

UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.backgroundColor = [UIColor redColor];
titleLabel.text = @"Each of these constraints can have its own priority. By default, ";
titleLabel.font = [UIFont systemFontOfSize:17];
[contentView addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(contentView.mas_top);
make.left.equalTo(contentView.mas_left).offset(16);
}];

UILabel *timeLabel = [[UILabel alloc] init];
timeLabel.backgroundColor = [UIColor greenColor];
timeLabel.text = @"2017/03/12 18:20:22";
timeLabel.font = [UIFont systemFontOfSize:17];
[contentView addSubview:timeLabel];
[timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(titleLabel.mas_top);
make.left.equalTo(titleLabel.mas_right).offset(8);
make.right.lessThanOrEqualTo(contentView.mas_right).offset(-8);
}];

if (b) {
[timeLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
// 或
//[titleLabel setContentHuggingPriority:UILayoutPriorityFittingSizeLevel forAxis:UILayoutConstraintAxisHorizontal];
}

return contentView;
}

二、iOS使用topLayoutGuide和bottomLayoutGuide

参考文章:iOS使用topLayoutGuide和bottomLayoutGuide

在iOS中,可以使用topLayoutGuide和bottomLayoutGuide来适配屏幕内容,它们是属于UIViewController的属性,配合masonry和SnapKit等约束工具,效果更好。

1
2
3
4
5
6
7
8
UIView *bottomPayView = [[UIView alloc] init];
bottomPayView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bottomPayView];
[bottomPayView mas_makeConstraints:^(MASConstraintMaker *x) {
x.height.equalTo(@45);
x.left.right.equalTo(self.view);
x.bottom.equalTo(self.mas_bottomLayoutGuide);
}];

三、UITableView自动计算cell高度并缓存,再也不用管高度啦

UITableView自动计算cell高度并缓存,再也不用管高度啦

用xib加约束和用masonry加代码约束都是可以的。注意约束一定要自上而下加好,让系统知道怎么去计算高度。

加好约束后,然后告诉tableView自己去适应高度就可以了。有两种写法:

1
2
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 100;

或者直接写这个代理方法就可以了

1
2
3
4
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 100;
}

这个的意思就是告诉tableView,你需要自己适应高度,我不给你算啦哈哈哈。但是我们需要告诉它一个大概高度,例如上面的100,理论上这个是可以随便写的,并不影响显示结果,但是越接近真实高度越好。

可能遇到的问题和解决办法

1.高度不对
有时候有可能运行出来后看到cell的高度显示的不对。这个问题是因为约束没有满足自上而下,从而系统不知道怎么去计算。解决办法就是去修改约束,直到满足为止。一定要好好理解约束啊!

2.点击状态栏无法滚动到顶部
我们知道,如果界面中有UIScrollView的话,点击状态栏会让其滚动到顶部,就像这样:

但是如果我们用了自动计算高度的方法,又调用了tableView的reloadData方法(例如我们的数据有分页的时候,加载完下一页的数据后会去刷新tableView)。这时候就会出现问题,点击状态栏就有几率不能精确滚动到顶部了:

解决这个问题的办法是去缓存cell的高度,代码如下:

1
@property (nonatomic, strong) NSMutableDictionary *heightAtIndexPath;//缓存高度所用字典
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma mark - UITableViewDelegate
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height)
{
return height.floatValue;
}
else
{
return 100;
}
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber *height = @(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}

四、问题

问题1:使用Masonry的时候进行updateConstraints没有效果

原因:使用updateConstraints更新的时候必须是makeConstraints里面设置过的约束。但如果只是这样还不行,还需要约束对象匹配才能成功。

问题详见:使用Masonry的时候进行updateConstraints没有效果

iOS11适配-Safe Area

iOS11适配-Safe Area

在iOS 11,UIViewController中的UIView的topLayoutGuide和bottomLayoutGuide被替换成了新的安全区属性。

1
2
3
4
5
6
> @available(iOS 11.0, *)
> open var safeAreaInsets: UIEdgeInsets { get }
>
> @available(iOS 11.0, *)
> open var safeAreaLayoutGuide: UILayoutGuide { get }12345
>

safeAreaInsets属性意味着屏幕可以被任何方向遮挡,并不只是上下,当iPhone X出现时,我们就明白了为什么我们需要对左右两边也进行缩进。

Masonry动画