为什么使用masonry不能立即获取frame?

无敌鲨鱼炮
  • 448

第一段代码:

[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
}];
NSLog(@"%@", self.scrollView);

结果是:<UIScrollView: 0x7fcae504ba00; frame = (0 0; 0 0);

第二段代码:

[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
}];
// 延迟0.1秒后获取frame
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"%@", self.scrollView);
});

结果是:<UIScrollView: 0x7fad83820a00; frame = (0 0; 375 667);

问:

为什么延迟0.1秒后就能获取到正确的frame了?

回复
阅读 6.8k
8 个回答

推荐一篇 博文 ,讲得挺不错的!

autolayout的话,如果你是在一个viewController里面,一般是在viewDidLayoutSubviews才能生效,如果是一个view的话,一般则在layoutSubviews里面才生效

[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make){
    make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
}];

下面加上这句就可以了,

[self.scrollView.superview layoutIfNeeded];

这样就能获取到self.scrollViewframe了。

结合Masonry源码,在View+MASAdditions.m文件中的- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block方法里打个断点,就很清晰了。
第一段代码,先走

[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.mas_equalTo(UIEdgeInsetsMake(0, 0, 0, 0));
}];

mas_makeConstraints:方法中注册block,调用MASConstraintMaker的 install方法,建立约束。
然后走

NSLog(@"%@", self.scrollView);

再到mas_makeConstraints方法中实现block。
因为Masonry中block注册的时间是作用域结束,就是大括号末尾。
dispatch_after方法中,block注册的时机,要更延后一个设定的间隔。
为什么Masonry中block注册的时间是这样的,请看网上的Masonry源码解析。
通过断点,能够很直观地看到。

他是用的AutoLayout 啊,自动布局 是拿不到精确的frame 的吧

先来一句概括的话,咱们写代码也是要讲究基本法的. 嗯,应该是讲究基本流程.

首先需要明白从 Constraints 对 最后的布局完成,肯定有一个转化成 frame 的过程.
上面一些回答中说到的 viewLayout 即是这个过程已经完成时调用相应方法的地方.

可能你还有疑问就是,所有的 UI 代码都是在主线程工作的.
那么当调用 NSLog 的时候是不是随着 makeConstraints 或 updateConstraint 的调用就完成了呢?
因为我们知道随着约束的变化, autoLayoutEngine 肯定会因此来更新对应的约束的值的. 如果这一切是同步的.
那在你调用 NSLog 时 它的 frame 就应该变化了.

但是并不是的, UI 系统并不是随着每一个 UI 变化的调用同步执行的.
UI刷新是有固定的频率的. 假设以 30 FPS 为例. 也就是系统可能需要过 0.03秒之后才会刷新 UI,
处理你对 UI 的请求变化.
你提到的 0.1 秒其实已经过了好几个 VSYNC阶段了,所以 frame 已经计算出来了.
其实你过 0.05秒也是可以得到其 frame 的.

核心点在于 UI刷新的工作流程, VSYNC

何锦涛
  • 5
新手上路,请多包涵

等到调用了viewWillLayoutSubviews 才会拿到你想要的frame,需要时间

你知道吗?

宣传栏