UIView 的 frame 原理
0x0. 什么是 frame
来看下官方定义:
The frame rectangle, which describes the view’s location and size in its superview’s coordinate system.
从这句话中可以提取出三个关键信息: 矩形、位置 和 大小。
也就是说,不管 view 的形状如何,frame 都一定是矩形的。而且,只要 frame 发生了变化,那么 view 的位置或者大小就一定会发生变化。 看似是一句废话,但这句话对接下来的 frame 分析却有一定的帮助。
0x1. frame 的计算方式
// demo1
self.myView = [[UIView alloc] init];
self.myView.layer.anchorPoint = CGPointMake(0, 0);
self.myView.frame = CGRectMake(12, 14, 120, 80);
self.myView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.myView];
2
3
4
5
6
// demo2
self.myView = [[UIView alloc] init];
self.myView.frame = CGRectMake(12, 14, 120, 80);
self.myView.layer.anchorPoint = CGPointMake(0, 0);
self.myView.backgroundColor = [UIColor redColor];
[self.view addSubview:self.myView];
2
3
4
5
6
仔细观察下这段代码的不同之处。
没错,就是设置 frame 和 anchorPoint 的先后顺序不一样而已。运行后效果图如下:
视图位置不同,说明 frame 是不同的。demo1 为{12, 14, 120, 80}
,而 demo2 因为在设置完 frame 后改变了 anchorPoint,导致 frame 变成了{72, 54, 120, 80}
,所以视图位置向右下方偏移。
通过断点调试,可以知道-[UIView setFrame:]
内部调用了以下三个方法:
-[CALayer setFrame:]
-[CALayer setPosition:]
-[CALayer setBounds:]
self.myView = [[MyView alloc] init];
self.myView.frame = CGRectMake(12, 14, 120, 80);
self.myView.backgroundColor = [UIColor redColor];
[backView addSubview:self.myView];
2
3
4
运行上述代码,在-[CALayer setPosition:]
断住,看下相关逻辑:
上图中d8
(双精度浮点寄存器) 中的值是 width,d14
中的值是 anchorPoint.x,d11
中的值是 origin.x,d9
中的值是 height,d15
中的值是 anchorPoint.y,d10
中的值是 origin.y。根据这些值,计算出 position 后,作为 setPosition:
方法的参数。
通过fmadd
指令,可以清楚地看到 position、anchorPoint、frame 三者之间的关系:
position.x = frame.origin.x + anchorPoint.x * frame.size.width;
position.y = frame.origin.y + anchorPoint.y * frame.size.height;
2
经过一系列测试验证后,发现该等式是成立的。那么对于上述 demo2 为什么改变了 anchorPoint 而 frame 发生变化也就不言而喻了。
frame.origin.x = position.x - anchorPoint.x * frame.size.width
= 72 - 0 * 120
= 72;
frame.origin.y = position.y - anchorPoint.y * frame.size.height
= 54 - 0 * 80
= 54;
2
3
4
5
6
7
0x2. 带有 transform 的 frame
文章开头提到,frame 表示的是一个矩形框架。那么对 view 应用仿射变换后,frame 则是刚好能包裹住视图的那个矩形。
myView.frame = CGRectMake(50, 50, 100, 100);
myView.transform = CGAffineTransformMakeRotation(M_PI_4); // 顺时针旋转45°
2
此时,view 的 bounds 依然是{0, 0, 100, 100}
,但 frame 却变成了{29.29, 29.29, 141.42, 141.42}
。
frame.width 和 frame.height 刚好等于 bounds.width 与 bounds.height 平方和的平方根。套用下公式:
// 当然系统可能并非通过这样方式来计算 frame.width 和 frame.height,总感觉有点low
frame.size.width = √(100^2 + 100^2) = 141.42;
frame.size.height = √(100^2 + 100^2) = 141.42;
frame.origin.x = 100 - 0.5 * 141.42 = 29.29;
frame.origin.y = 100 - 0.5 * 141.42 = 29.29; // 锚点默认值为0.5
2
3
4
5
0x3. 小扩展
0x3.1 获取 view 的 frame
通过断点调试可以看出,-[UIView frame]
方法内部实现仅仅是调用了-[CALayer frame]
:
也就是说,当获取 view 的 frame 时,其实是返回了 layer 的 frame。
0x3.2 view.center 和 layer.position
view 的center
属性跟 layer 的position
属性是相对应的,两者是同步变化的。-[UIView setCenter:]
方法内部会调用-[CALayer setPosition:]
,而-[UIView center]
其实就是返回了 layer 的position
。(只想说,汇编面前没有秘密)
有个值得注意的点,就是 view.center 只有两种情况下会发生变化:
- 直接改变了 view.center (或改变 layer.position)
- 直接改变了 view.frame (或改变 layer.frame)
记住这点,套用公式便可知道下列代码的输出结果了:
self.myView = [[MyView alloc] init];
self.myView.frame = CGRectMake(12, 14, 120, 80);
// position.x = 12 + 0.5 * 120 = 72
// position.y = 14 + 0.5 * 80 = 54
self.myView.layer.anchorPoint = CGPointMake(0, 0);
// anchorPoint.x = 0
// anchorPoint.y = 0
self.myView.bounds = CGRectMake(0, 0, 100, 60);
// frame.size.width = 100
// frame.size.height = 60
// frame.origin.x = 72 - 0 * 100 = 72
// frame.origin.y = 54 - 0 * 60 = 54
self.myView.backgroundColor = [UIColor redColor];
[backView addSubview:self.myView];
NSLog(@"frame: %@ center: %@ ", NSStringFromCGRect(self.myView.frame), NSStringFromCGPoint(self.myView.center));
// frame: {72, 54, 100, 60} center: {72, 54}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20