[iOS] frame、bounds以及anchorPoint的联系

这篇的起源是我们的小哥哥特别坏(明明和川川),出了一堆这种问题,于是我发现好多人都不会,就想统一写一篇~

UIView中用于表征视图在父视图中显示出来的位置和尺寸的属性是frame。 同时系统还提供另外两个属性centerbounds其中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,仍旧是以中心为轴放大的。

tranform以后改bounds

因为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都没有变,然鹅显示变成什么样子了呢?

改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看起来向右下角移动了。

放大以后改bounds

这个时候就很神奇了,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倍。

改transform以后改bounds (为了view1可以不被覆盖,改了view2的透明度)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351