这篇的起源是我们的小哥哥特别坏(明明和川川),出了一堆这种问题,于是我发现好多人都不会,就想统一写一篇~
UIView中用于表征视图在父视图中显示出来的位置和尺寸的属性是frame
。 同时系统还提供另外两个属性center
和bounds
。其中center
属性值描述视图的中心点在父视图中的位置,而bounds
属性的size部分则描述视图本身固有的尺寸。
需要注意的是bounds
属性中的origin
部分描述的是视图内部坐标系中原点的位置,它影响着里面子视图的位置。除此之外,系统还提供一个transform
属性来实现视图的仿射变换: 比如平移、缩放、旋转、倾斜的效果。
在这四个属性中,除了frame属性是计算属性外,其他三个属性都是实体属性。frame的值是依赖这三个属性计算出来的
。在介绍frame的计算公式前先要了解三个概念:图层(CALayer)、锚点(Anchor Point)、仿射变换。
Q1: view1里面有个view2,当让view1的transform变为放大两倍以后,这两个view的frame和bounds分别如何变化?
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColor.whiteColor;
view1 = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view1.backgroundColor = UIColor.blueColor;
[self.view addSubview:view1];
view2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
view2.backgroundColor = UIColor.yellowColor;
[view1 addSubview:view2];
[self test1];
NSLog(@"view1 frame: %@, bounds: %@", @(view1.frame), @(view1.bounds));
NSLog(@"view2 frame: %@, bounds: %@", @(view2.frame), @(view2.bounds));
}
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
}
然后我们来看下打印的结果:
2020-08-10 16:10:05.411854+0800 Example1[20053:925344] view1 frame: NSRect: {{50, 50}, {200, 200}}, bounds: NSRect: {{0, 0}, {100, 100}}
2020-08-10 16:10:05.412088+0800 Example1[20053:925344] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
这个结果引发了一些问题:
- 为什么view1的frame变了但是bounds的size没有变?
- 为什么view2的表现看起来变大了两倍但是frame和bounds都没有变?
由于frame是由四个属性决定的,所以当让view1的transform变为两倍scale的时候,frame会以中心为anchor进行四个方向的等比放大,所以frame就变为了{{50, 50}, {200, 200}}
,但是bounds和transform是独立的,所以并没有被改变,仍旧是{{0, 0}, {100, 100}}
。
那么view2为什么看起来变大了,但是frame没有变呢?我打印了一下两个view的transform看了一下:
CGAffineTransform trans2 = view2.transform;
CGAffineTransform trans1 = view1.transform;
Printing description of trans2:
(CGAffineTransform) trans2 = (a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0)
Printing description of trans1:
(CGAffineTransform) trans1 = (a = 2, b = 0, c = 0, d = 2, tx = 0, ty = 0)
所以其实view2虽然看起来变大了,但是只是因为父view变大了,自己的transform是没有的,所以frame并没有变化,bounds也没有改变。
那view2是如何看起来变大的呢,其实是因为view1变大了,而view2的frame相对于view1的bounds里面的宽高仍旧是1/2的关系(这里需要用同一个坐标系的比较,frame是相对于父view的bounds而言的),所以当view1变大的时候,view2看起来也会变大,维持1/2的比例。
Q2: view1里面有个view2,当让view1的transform变为放大两倍以后 & view2也放大两倍,这两个view的frame和bounds分别如何变化?
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
view2.transform = CGAffineTransformMakeScale(2, 2);
}
输出:
2020-08-10 17:59:25.589782+0800 Example1[21109:961497] view1 frame: NSRect: {{50, 50}, {200, 200}}, bounds: NSRect: {{0, 0}, {100, 100}}
2020-08-10 17:59:25.589911+0800 Example1[21109:961497] view2 frame: NSRect: {{25, 25}, {100, 100}}, bounds: NSRect: {{0, 0}, {50, 50}}
这里其实就符合了Q1的理论,由于你也改了view2的transform,所以view2的frame里面的宽高都会double,并且会围绕它的anchorPoint也就是中点变化。然后bounds也是不会发生变化的因为没有改。
比较奇怪的是view2的frame里的宽高是100,而view1的是200,但是两者看起来是一样大的。这是因为其实view2的frame相对view1的bounds是1:1的关系,所以两者大小一致。
当一个视图设置了非CGAffineTransformIdentity的仿射变化值后,我们不能再通过设置frame属性的值来修改视图的位置和尺寸了,否则最终展示的效果未可知。 因此当对视图设置了仿射变换属性后,如果需要调整视图的位置和尺寸时我们需要操作的是center属性和bounds属性而不能再操作frame属性了。
Q3: view1改transform变为放大两倍以后,再改frame会发生什么,这个view1的bounds如何变化?
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
view1.frame = CGRectMake(50, 50, 100, 100);
}
输出:
2020-08-10 20:15:50.761962+0800 Example1[22097:993278] view1 frame: NSRect: {{50, 50}, {100, 100}}, bounds: NSRect: {{0, 0}, {50, 50}}
2020-08-10 20:15:50.762141+0800 Example1[22097:993278] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
可以看到在改了transform以后再改frame会很神奇,bound也被修改了,而且和我们改的frame不一致,感觉是等比缩小了。所以不建议改了transform以后还改frame哦
Q4: view1里面有个view2,当让view1先改transform变为放大两倍以后,再改view1的bounds,这两个view的frame和bounds分别如何变化?
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
view1.bounds = CGRectMake(0, 0, 150, 150);
}
输出:
2020-08-10 21:16:56.187782+0800 Example1[22600:1012833] view1 frame: NSRect: {{0, 0}, {300, 300}}, bounds: NSRect: {{0, 0}, {150, 150}}
2020-08-10 21:16:56.187976+0800 Example1[22600:1012833] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
view2因为没有动它,所以仍旧没有变化。view1虽然后改的bounds,但是之前的transform仍旧有效,所以在改了bounds以后,frame根据bounds以及transform来计算,也就是我们设置的150*2=300,于是宽高都变成了300,仍旧是以中心为轴放大的。
因为view1放大的时候其实本来view2是在右下角跟着放大的,但是当我们改了view1的bounds宽高的时候,他是不会带着view2一起改的,所以就变成酱紫了~ view2就不再在右下角了
transform改scale会让子view看起来跟着变,但是如果是改bounds的宽高只会有自己变哦,子view们不会动
Q5: view1里面有个view2,当让view1先改transform变为放大两倍以后,再改view1的bounds,但这次会改bounds的原点,这两个view的frame和bounds分别如何变化?
首先我们先考虑只改view1的bounds的原点:
- (void)test1 {
view1.bounds = CGRectMake(-100, -100, 150, 150);
}
输出:
2020-08-10 21:23:07.974233+0800 Example1[22730:1017174] view1 frame: NSRect: {{75, 75}, {150, 150}}, bounds: NSRect: {{-100, -100}, {150, 150}}
2020-08-10 21:23:07.974496+0800 Example1[22730:1017174] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
可以看到view1如愿以偿的放大了,view2的frame和bounds都没有变,然鹅显示变成什么样子了呢?
然鹅显示上view2移动了,这是为什么呢?因为当你改了父view的bounds原点的时候,父view的原点相对于父view坐标系变成了(-100, -100),但父view原点是固定不会动的,动的是父view坐标系整体向右下角移动100,此时在父view坐标系里面的view2也就向右下角移动了,但它和父view坐标系的相对位置是没有变化的,所以子view的frame并没有变。
view2的位置是怎么算出来的呢?view1的左上角的坐标变为了(-100, -100),然后view2的frame是(50, 50, 50, 50),所以view2左上角距离view1左上角的水平距离就是100+50=150,刚好是view1的宽度,所以就变成现在的样纸啦。
然后我们来考虑先放大再改bounds的原点参考:
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
view1.bounds = CGRectMake(-100, -100, 150, 150);
}
输出:
2020-08-10 21:30:16.489397+0800 Example1[22858:1021418] view1 frame: NSRect: {{0, 0}, {300, 300}}, bounds: NSRect: {{-100, -100}, {150, 150}}
2020-08-10 21:30:16.489518+0800 Example1[22858:1021418] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
得到的结果就很类似了,view2仍旧没有变化,只是view1放大了,然后view2看起来向右下角移动了。
这个时候就很神奇了,view2左上角的坐标仍旧是(50, 50),view1左上角的坐标相对于它自己的坐标系是(-100, -100),于是按理说,view2左上角距离view1左上角的水平距离仍旧应该是100+50=150,不应该是上图那样子的显示啊,但不要忘了一个问题,在view1的坐标系里面,他自己的宽高就是150,所以view2左上角距离view1左上角的水平距离就是差出了一个view1的宽高的。于是就变成了上图的样子啦。
Q6: view1里面有个view2,当让view1先改anchorPoint,然后改transform变为放大两倍以后,这两个view的frame和bounds分别如何变化?
首先看下如果只改动anchorPoint是什么样子的?
- (void)test1 {
view1.layer.anchorPoint = CGPointMake(0, 0);
}
输出:
2020-08-10 19:58:57.373654+0800 Example1[21791:982107] view1 frame: NSRect: {{150, 150}, {100, 100}}, bounds: NSRect: {{0, 0}, {100, 100}}
2020-08-10 19:58:57.373854+0800 Example1[21791:982107] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
可以看到view1移动了,因为改anchorPoint会保持position不变,改之前的anchorPoint在中点,position为(150, 150),改之后anchorPoint为左上角,且坐标为(150, 150),所以view1的frame变为{{150, 150}, {100, 100}}
。
然后我们放大它:
- (void)test1 {
view1.layer.anchorPoint = CGPointMake(0, 0);
view1.transform = CGAffineTransformMakeScale(2, 2);
}
输出:
2020-08-10 20:10:55.854049+0800 Example1[21975:989495] view1 frame: NSRect: {{150, 150}, {200, 200}}, bounds: NSRect: {{0, 0}, {100, 100}}
2020-08-10 20:10:55.854260+0800 Example1[21975:989495] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
它维持了anchorPoint的位置不变然后进行了放大,所以frame变为了{{150, 150}, {200, 200}}
。
Q7: view1里面有个view2,当让view1先改transform变为放大两倍以后,再改view2的bounds,这两个view的frame和bounds分别如何变化?
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
view2.bounds = CGRectMake(0, 0, 150, 150);
}
输出:
2020-08-10 21:53:13.953252+0800 Example1[23270:1036055] view1 frame: NSRect: {{50, 50}, {200, 200}}, bounds: NSRect: {{0, 0}, {100, 100}}
2020-08-10 21:53:13.953437+0800 Example1[23270:1036055] view2 frame: NSRect: {{0, 0}, {150, 150}}, bounds: NSRect: {{0, 0}, {150, 150}}
view1仍旧是固定中心放大两倍就是frame: NSRect: {{50, 50}, {200, 200}}, bounds: NSRect: {{0, 0}, {100, 100}}
,然后view2这个时候的frame是根据新的bounds计算的,因为新的比旧的bounds里面的宽高大,所以也是需要以中心(75, 75)为固定点放大,于是frame会变为{{0, 0}, {150, 150}}
,显示上而言,由于view1的bounds里面的宽高是100,但view2的frame里面的宽高是150,所以view2会和view1左上角重合,并且宽高都是view1的1.5倍。
Q8: view1里面有个view2,当让view1先改transform变为放大两倍以后,再改view2的frame,这两个view的frame和bounds分别如何变化?
这个其实很常规,因为view2本身并没有transform,所以就很容易可以得到:
- (void)test1 {
view1.transform = CGAffineTransformMakeScale(2, 2);
view2.frame = CGRectMake(10, 10, 150, 150);
}
输出:
2020-08-11 07:40:23.436102+0800 Example1[25379:1126992] view1 frame: NSRect: {{50, 50}, {200, 200}}, bounds: NSRect: {{0, 0}, {100, 100}}
2020-08-11 07:40:23.436298+0800 Example1[25379:1126992] view2 frame: NSRect: {{10, 10}, {150, 150}}, bounds: NSRect: {{0, 0}, {150, 150}}
Q9: view1里面有个view2,当让view1先改anchorPoint,再改frame,然后transform变为放大两倍以后,这两个view的frame和bounds分别如何变化?
其实吧这个的分析流程和上面总体是一致的,首先改了anchorPoint为(0, 0)以后,view1的frame其实是会变的,因为position (150, 150)是固定的但是anchorPoint变了,于是frame会变为(150, 150, 100, 100)。
然后是改frame为(200, 200, 200, 200),此时view1的宽高变了,他需要固定锚点放大到200的宽高,并且左上角的坐标变为(200, 200),bounds就会变为(0, 0, 200, 200)。
然后此时让view1的transform变为两倍大,那么它会固定anchorPointment进行缩放,所以frame变为(200, 200, 400, 400),而bounds由于没有改,所以仍旧是(0, 0, 200, 200)。
- (void)test1 {
view1.layer.anchorPoint = CGPointMake(0, 0);
view1.frame = CGRectMake(200, 200, 200, 200);
view1.transform = CGAffineTransformMakeScale(2, 2);
}
输出:
2020-08-11 08:06:42.291840+0800 Example1[25766:1142786] view1 frame: NSRect: {{200, 200}, {400, 400}}, bounds: NSRect: {{0, 0}, {200, 200}}
2020-08-11 08:06:42.292020+0800 Example1[25766:1142786] view2 frame: NSRect: {{50, 50}, {50, 50}}, bounds: NSRect: {{0, 0}, {50, 50}}
view2仍旧是因为没有对他的操作所以没有变化~
题外话
下面是我在reference的一篇里面看到的一句话,觉得很反常识:
AutoLayout在完成布局后,所计算出来的位置和尺寸内部修改的值是center和bounds两个属性,因此最终的展示效果不会因为仿射变换而产生异常。同时这也解释了为什么通过AutoLayout设置约束后修改frame属性来改变位置和尺寸不会起作用的原因。
这句话是什么意思呢?其实就是使用autolayout的话如果再改transform可能会奇奇怪怪的,但是iOS8以后已经不会啦,可以参考:https://www.cnblogs.com/JYun/p/4740617.html
欢迎参考:
https://blog.csdn.net/zhonggaorong/article/details/51218427
https://www.jishudog.com/1961/html