因为使用简单,autoresizing的自动布局方式一直挺受我偏爱的。然而前端时间测试的时候,出现了几个布局上的bug,调试后发现原来是没有用正确姿势使用autoresizing所致的(╥﹏╥)。
为了描述一下这个bug,假装我写了这样一段代码:
CGRect parentFrame = CGRectMake(0, 0, 100, 100);
UIView *parent = [[UIView alloc] initWithFrame:parentFrame];
parent.backgroundColor = [UIColor redColor];
[self.view addSubview:parent];
UIView *son = [[UIView alloc] initWithFrame:CGRectMake(0, 50, 100, 50)];
son.backgroundColor = [UIColor blueColor];
son.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[parent addSubview:son];
[parent setFrame:CGRectMake(0, 0, 100, 0)];
[parent setFrame:parentFrame];
大意是,希望son
这个视图的底部永远和parent
视图的底部对齐,而son
视图的顶部永远和parent
视图的顶部有50
单位的距离。也就是这样的结果:
但是运行完这段代码后我得到的结果是这样的:
于是bug就产生了(╥﹏╥)
为什么会这样呢?明明正确设置了autoresizing参数,可最后得到了错误的布局结果。仔细一想,原来是因为其中经历了
[parent setFrame:CGRectZero];
这个步骤。
在我的理解中,如果仅有UIViewAutoresizingFlexibleHeight
,则表示,无论parent
视图的尺寸如何变化,son
视图的上边界和parent
视图的上边界的距离不变,son
视图的下边界和parent
视图的下边界的距离不变,写成公式可能就是:
CGRectGetMinY(parent.bounds) - CGRectGetMinY(son.frame) 不变
CGRectGetMaxY(parent.bounds) - CGRectGetMaxY(son.frame) 不变
最开始,parent
视图的bounds
为(0, 0, 100, 100)
,而son
视图的frame
为(0, 50, 100, 50)
,也就是说:
CGRectGetMinY(parent.bounds) - CGRectGetMinY(son.frame) = -50
CGRectGetMaxY(parent.bounds) - CGRectGetMaxY(son.frame) = 0
后来,parent
视图的bounds
变为(0, 0, 0, 0)
,此时若输出son
视图的frame,会发现frame = (0 50; 0 0)
。也就是说这个时候
CGRectGetMinY(parent.bounds) - CGRectGetMinY(son.frame) = -50
CGRectGetMaxY(parent.bounds) - CGRectGetMaxY(son.frame) = -50
这个结果和我所理解的UIViewAutoresizingFlexibleHeight
矛盾了。
矛盾就出在,如果要严格按照“不变”的规则行事的话,此时son.frame.size.height
应该为-50
才对。
但是尺寸怎么可以是负值呢,可能autoresizing出于这个考虑,将-50
的高度处理成了0
。这也就说明,在程序执行到这一步的时候,失去了-50
这个信息。
最后,当parent
视图的bounds
回到(0, 0, 100, 100)
的时候,autoresizing保持了
CGRectGetMinY(parent.bounds) - CGRectGetMinY(son.frame) = -50
CGRectGetMaxY(parent.bounds) - CGRectGetMaxY(son.frame) = -50
这个关系不变,导致了son
视图的高度最终变成了100
。看一眼代码最终执行的结果,果然是这样。
所以,我的bug就出在,同事因为他的业务需要,改变了我负责的最外层视图的frame,而后又将其改回原值(他以为什么也不会发生,我开始也这么以为……),这个过程中,我负责的子视图在autoresize时出现了刚才的问题,导致最后呈现出了错误的布局。
在解决这个问题的时候,我还想到了,如果autoresizing不将负值的宽高转换成0,会怎样呢?
试了试,发现UIView
会自动将负值的宽高转换成正值。
比如执行完这几行代码后
UIView *view = [[UIView alloc] init];
CGRect rect = CGRectMake(100, 100, -50, -50);
view.frame = rect;
打印view
视图,会发现
<UIView: 0x7ff88bd4db00; frame = (50 50; 50 50); layer = <CALayer: 0x7ff88bd4d330>>
view
视图的frame
被转换了。
所以不管怎么样,使用autoresizing时,如果不小心让某个视图的宽高变成了负值,则一定会损失一部分信息,导致最后即便父视图的frame
回到原值,子视图的frame
也会出现问题。