Masonry布局控件frame为0的问题

Masonry 是对 autolayout 的封装,优雅的链式语法和简洁易用的接口让我们在做UI开发时节省了不少时间。然而在初次使用它时,由于还对 autolayout 理解不够深,就遇到了一些问题,比如使用 Masonry 对控件添加约束后,并不会立即生效,frame 仍然是 0。如果此时我们需要这个 frame,应该怎么做呢?

1
2
3
4
5
6
7
8
9
10
11
UIView *parent = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIImageView *child = [UIView alloc] init];
[parent addSubview:child];
[child mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(20,20));
make.top.left.mas_equalTo(50);
}];
NSLog(@"%@",redView);

打印结果:
** <UIImageView: 0x7fb222605550; frame = (0 0; 0 0); layer = <CALayer: 0x7fb22260b3a0>>**

使用 Masonry 添加约束后,如果有要设置 child 的形状为圆形,就得知道它的 frame,像下面这样写肯定不会设置成功的:

1
2
3
4
5
6
[child mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(20,20));
make.top.left.mas_equalTo(50);
}];
child.layer.cornerRadius = child.bounds.size.width/2;
child.layer.masksToBounds = YES; //设置头像为圆形

因为这时候的 frame 还是 0。曾天真的想,会不会是因为 block 中的处理是在异步线程进行的,不会等待 block 执行完就已经走到了下面使用frame的代码,(一阵狂喜,好聪明。。。),所以马上把代码改写:

1
2
3
4
5
6
[child mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(20,20));
make.top.left.mas_equalTo(50);
child.layer.cornerRadius = child.bounds.size.width/2;
child.layer.masksToBounds = YES; //设置头像为圆形
}];

然而并没有什么卵用。

想不通的时候,就只能请教 goole 了,然后找到了 Masonry 约束下获取 frame 的方法

masonry 本就是对 autolayout 的封装,使用 Masonry 就等于使用了苹果的 autolayout。使用 masonry 布局完之后,系统会在某个时间点调用各个 view 的 layoutSubViews 方法,从而更新各个控件的frame。遗憾的是,frame 的更新并不会在刚执行完 Masonry 布局代码时立即进行,在布局代码的下一行,你所获取到的 frame 仍然是0。

想要在布局代码结束就立即获取当前某个控件正确的 frame,需要调用layoutIfNeeded函数立即刷新布局,各个控件才会按照约束条件,生成当前布局相应的frame和bounds。而调用layoutIfNeeded的目的是让系统调用layoutSubviews方法,我们也可以直接在这个方法里获取frame,因为这时候开始layout subviews,Masonry已经计算出了真实的frame。

下面附上关于autolayout更新几个方法的区别:

  1. setNeedsLayout:标记页面需要更新,但是什么时候才会调用
    layoutSubviews 去刷新布局,就不一定了。

  2. layoutIfNeeded:告知页面如果需要,就立刻更新布局。这里的“如果需要”什么意思呢?有什么条件吗?是的,只有满足如下某一个或几个条件,调用 layoutIfNeeded 才会立即刷新frame:

  • 有 addSubview 操作
  • 设置了view的 frame,当然前提是设置前后 frame 的值发生了变化
  • 滚动一个UIScrollView
  • 旋转 Screen
  • 改变一个UIView大小的时候

如果不满足,就算是调用了 layoutIfNeeded 也不会立刻执行 layoutSubViews 进行 frame 更新。

如果我们想不管什么情况,都强制执行 layoutSubViews 进行 frame 更新怎么办呢?那就是同时调用下面这两个方法:

1
2
[self setNeedsLayout];
[self layoutIfNeeded];

这样必然会调用 layoutSubViews 。