[iOS] frame、bounds以及anchorPoint的聯(lián)系

這篇的起源是我們的小哥哥特別壞(明明和川川),出了一堆這種問題,于是我發(fā)現(xiàn)好多人都不會,就想統(tǒng)一寫一篇~

UIView中用于表征視圖在父視圖中顯示出來的位置和尺寸的屬性是frame。 同時系統(tǒng)還提供另外兩個屬性centerbounds。其中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,仍舊是以中心為軸放大的。

tranform以后改bounds

因為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都沒有變,然鵝顯示變成什么樣子了呢?

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

放大以后改bounds

這個時候就很神奇了,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倍。

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容