這篇的起源是我們的小哥哥特別壞(明明和川川),出了一堆這種問題,于是我發(fā)現(xiàn)好多人都不會,就想統(tǒng)一寫一篇~
UIView中用于表征視圖在父視圖中顯示出來的位置和尺寸的屬性是frame。 同時系統(tǒng)還提供另外兩個屬性center和bounds。其中center屬性值描述視圖的中心點在父視圖中的位置,而bounds屬性的size部分則描述視圖本身固有的尺寸。
需要注意的是bounds屬性中的origin部分描述的是視圖內(nèi)部坐標系中原點的位置,它影響著里面子視圖的位置。除此之外,系統(tǒng)還提供一個transform屬性來實現(xiàn)視圖的仿射變換: 比如平移、縮放、旋轉(zhuǎn)、傾斜的效果。
在這四個屬性中,除了frame屬性是計算屬性外,其他三個屬性都是實體屬性。frame的值是依賴這三個屬性計算出來的。在介紹frame的計算公式前先要了解三個概念:圖層(CALayer)、錨點(Anchor Point)、仿射變換。
Q1: view1里面有個view2,當讓view1的transform變?yōu)榉糯髢杀兑院?,這兩個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);
}
然后我們來看下打印的結(jié)果:
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}}

這個結(jié)果引發(fā)了一些問題:
- 為什么view1的frame變了但是bounds的size沒有變?
- 為什么view2的表現(xiàn)看起來變大了兩倍但是frame和bounds都沒有變?
由于frame是由四個屬性決定的,所以當讓view1的transform變?yōu)閮杀秙cale的時候,frame會以中心為anchor進行四個方向的等比放大,所以frame就變?yōu)榱?code>{{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的關(guān)系(這里需要用同一個坐標系的比較,frame是相對于父view的bounds而言的),所以當view1變大的時候,view2看起來也會變大,維持1/2的比例。
Q2: view1里面有個view2,當讓view1的transform變?yōu)榉糯髢杀兑院?& 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也是不會發(fā)生變化的因為沒有改。
比較奇怪的是view2的frame里的寬高是100,而view1的是200,但是兩者看起來是一樣大的。這是因為其實view2的frame相對view1的bounds是1:1的關(guān)系,所以兩者大小一致。
當一個視圖設(shè)置了非CGAffineTransformIdentity的仿射變化值后,我們不能再通過設(shè)置frame屬性的值來修改視圖的位置和尺寸了,否則最終展示的效果未可知。 因此當對視圖設(shè)置了仿射變換屬性后,如果需要調(diào)整視圖的位置和尺寸時我們需要操作的是center屬性和bounds屬性而不能再操作frame屬性了。
Q3: view1改transform變?yōu)榉糯髢杀兑院?,再改frame會發(fā)生什么,這個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變?yōu)榉糯髢杀兑院?,再改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根據(jù)bounds以及transform來計算,也就是我們設(shè)置的150*2=300,于是寬高都變成了300,仍舊是以中心為軸放大的。

因為view1放大的時候其實本來view2是在右下角跟著放大的,但是當我們改了view1的bounds寬高的時候,他是不會帶著view2一起改的,所以就變成醬紫了~ view2就不再在右下角了
transform改scale會讓子view看起來跟著變,但是如果是改bounds的寬高只會有自己變哦,子view們不會動
Q5: view1里面有個view2,當讓view1先改transform變?yōu)榉糯髢杀兑院?,再改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如愿以償?shù)姆糯罅?,view2的frame和bounds都沒有變,然鵝顯示變成什么樣子了呢?

然鵝顯示上view2移動了,這是為什么呢?因為當你改了父view的bounds原點的時候,父view的原點相對于父view坐標系變成了(-100, -100),但父view原點是固定不會動的,動的是父view坐標系整體向右下角移動100,此時在父view坐標系里面的view2也就向右下角移動了,但它和父view坐標系的相對位置是沒有變化的,所以子view的frame并沒有變。
view2的位置是怎么算出來的呢?view1的左上角的坐標變?yōu)榱?-100, -100),然后view2的frame是(50, 50, 50, 50),所以view2左上角距離view1左上角的水平距離就是100+50=150,剛好是view1的寬度,所以就變成現(xiàn)在的樣紙啦。
然后我們來考慮先放大再改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}}
得到的結(jié)果就很類似了,view2仍舊沒有變化,只是view1放大了,然后view2看起來向右下角移動了。

這個時候就很神奇了,view2左上角的坐標仍舊是(50, 50),view1左上角的坐標相對于它自己的坐標系是(-100, -100),于是按理說,view2左上角距離view1左上角的水平距離仍舊應(yīng)該是100+50=150,不應(yīng)該是上圖那樣子的顯示啊,但不要忘了一個問題,在view1的坐標系里面,他自己的寬高就是150,所以view2左上角距離view1左上角的水平距離就是差出了一個view1的寬高的。于是就變成了上圖的樣子啦。
Q6: view1里面有個view2,當讓view1先改anchorPoint,然后改transform變?yōu)榉糯髢杀兑院螅@兩個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變?yōu)?code>{{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的位置不變?nèi)缓筮M行了放大,所以frame變?yōu)榱?code>{{150, 150}, {200, 200}}。
Q7: view1里面有個view2,當讓view1先改transform變?yōu)榉糯髢杀兑院?,再改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是根據(jù)新的bounds計算的,因為新的比舊的bounds里面的寬高大,所以也是需要以中心(75, 75)為固定點放大,于是frame會變?yōu)?code>{{0, 0}, {150, 150}},顯示上而言,由于view1的bounds里面的寬高是100,但view2的frame里面的寬高是150,所以view2會和view1左上角重合,并且寬高都是view1的1.5倍。

Q8: view1里面有個view2,當讓view1先改transform變?yōu)榉糯髢杀兑院?,再改view2的frame,這兩個view的frame和bounds分別如何變化?
這個其實很常規(guī),因為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變?yōu)榉糯髢杀兑院?,這兩個view的frame和bounds分別如何變化?
其實吧這個的分析流程和上面總體是一致的,首先改了anchorPoint為(0, 0)以后,view1的frame其實是會變的,因為position (150, 150)是固定的但是anchorPoint變了,于是frame會變?yōu)?150, 150, 100, 100)。
然后是改frame為(200, 200, 200, 200),此時view1的寬高變了,他需要固定錨點放大到200的寬高,并且左上角的坐標變?yōu)?200, 200),bounds就會變?yōu)?0, 0, 200, 200)。
然后此時讓view1的transform變?yōu)閮杀洞?,那么它會固定anchorPointment進行縮放,所以frame變?yōu)?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在完成布局后,所計算出來的位置和尺寸內(nèi)部修改的值是center和bounds兩個屬性,因此最終的展示效果不會因為仿射變換而產(chǎn)生異常。同時這也解釋了為什么通過AutoLayout設(shè)置約束后修改frame屬性來改變位置和尺寸不會起作用的原因。
這句話是什么意思呢?其實就是使用autolayout的話如果再改transform可能會奇奇怪怪的,但是iOS8以后已經(jīng)不會啦,可以參考:https://www.cnblogs.com/JYun/p/4740617.html
歡迎參考:
https://blog.csdn.net/zhonggaorong/article/details/51218427
https://www.jishudog.com/1961/html