天天看點

深入淺出了解frame和bounds

frame的官方解釋如下:

The frame rectangle, which describes the view’s location and size in its superview’s coordinate system. This rectangle defines the size and position of the view in its superview’s coordinate system. Use this rectangle during layout operations to set the size and position the view. Setting this property changes the point specified by the center property and changes the size in the bounds rectangle accordingly. The coordinates of the frame rectangle are always specified in points.

它定義了一個view相對于父視圖坐标系的位置和大小,它會影響center屬性和bounds屬性的size。

先看一下它究竟是什麼?

它是一個CGRect類型,如下:

其中的origin就是該view的位置,它是一個CGPoint類型,也是一個結構體,包含了我們熟知的常用二維坐标系的x、y。根據x、y可以在坐标系裡面唯一确定一個點。如下圖:

深入淺出了解frame和bounds

這個坐标系和我們平時接觸的還不太一樣,它是向右向下為正方向。是以對于window來說,其原點是左上角,比如現在的頭像的起始坐标就是(200,40)。按照原來正常的坐标系來說,應該是(200,-40)。

在設定一個CGRect的時候,用到的方法是CGRectMake,其實作如下:

也就是自己在實作部分建立了一個rect,然後逐個指派。

關于frame,這裡要注意的一點就是:frame是相對于父視圖的坐标系來定位的。如果你這樣設定frame:(0,0,100,200),也就是在父視圖左上角添加了一個寬100,高200的子視圖(前提是沒有改變父視圖的bounds,接下來會有介紹bounds)。

The bounds rectangle, which describes the view’s location and size in its own coordinate system. The default bounds origin is (0,0) and the size is the same as the size of the rectangle in the frame property. Changing the size portion of this rectangle grows or shrinks the view relative to its center point. Changing the size also changes the size of the rectangle in the frame property to match. The coordinates of the bounds rectangle are always specified in points. Changing the bounds rectangle automatically redisplays the view without calling its drawRect: method. If you want UIKit to call the drawRect: method, set the contentMode property to UIViewContentModeRedraw.

Changes to this property can be animated.

它也是描述的是視圖的位置和大小,隻不過是在自己的坐标系上。也就是說它描述的是目前視圖相對于自身坐标系的位置和大小。

舉個例子:

輸出的結果如下:

由此可見,如果我們沒有去更改bounds的值,它預設的位置坐标點是(0,0)。

The center point of the view's frame rectangle. The center point is specified in points in the coordinate system of its superview. Setting this property updates the origin of the rectangle in the frame property appropriately. Use this property, instead of the frame property, when you want to change the position of a view. The center point is always valid, even when scaling or rotation factors are applied to the view's transform.

center是view的中點。該屬性是想歸于父類的坐标系确定的。從bounds小節裡面的例子可以看到center的值,其計算方法為:

center.x = frame.origin.x + frame.size.width/2

center.y = frame.origin.y + frame.size.height/2

Specifies the transform applied to the view, relative to the center of its bounds. Use this property to scale or rotate the view's frame rectangle within its superview's coordinate system. (To change the position of the view, modify the center property instead.) The default value of this property is CGAffineTransformIdentity. Transformations occur relative to the view's anchor point. By default, the anchor point is equal to the center point of the frame rectangle. To change the anchor point, modify the anchorPoint property of the view's underlying CALayer object. In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.

它用于指定視圖的變換。使用這個屬性可以放大或者旋轉視圖,它的frame會是以改變,是以中心點為變換的。看例子:

看輸出的結果:

如圖:

深入淺出了解frame和bounds

可以看出,當我們對圖像通過旋轉,旋轉後的圖檔的frame已經變成了{(2.5773352536321568, 59.226689885086444), (314.84532949273569, 449.54662022982711)},此時的起始位置為圖上旋轉後标的(2.58,59.2),大小也變成了雙箭頭黑線标注的大小。

是以得出結論:進行了transform變換,其frame改變了,但是其bounds和center并沒有修改。此時bounds的size和frame的size已經沒有關系了。當沒有進行任何transform時,frame的size總是和bounds相等。

以上便是對frame、bounds、center和transform做了一個簡單的介紹。

接下來看一個例子(例子A):

這裡在parentView上添加了一個childView,然後對parentView的bounds進行修改和不修改進行了測試,結果如下:

深入淺出了解frame和bounds

你會發現當修改了parentView的bounds之後,發現childView缺向右向下做了偏移。這裡設定parentView的bounds的origin為(-40,-40)為何會發生這種情況呢?接下來先看一下下面這張圖:

+代表正方向,-代表負方向。

如果此時我們沒有改變圖中O的坐标,那麼此時A的坐标是(20,20),如果我們更改了O的坐标為(-20,-20),那麼原來A點的坐标就成了A'(0,0),但是A坐标是不變的,是以它會到黑色A處。是以你改變了原點坐标為負之後,A點會移動到黑色A。相反如果你設定了坐标原點為(20,20),那麼A點就會和坐标原點重合。

這就是為什麼childView會向右向下移動的原因。

接下來再做如下操作(例子B):

輸出結果如下:

運作效果是childView向上移動,然後停止。結果前後對比圖如下:

深入淺出了解frame和bounds

直覺來看,按說childView的frame改變了,但是從console輸出的結果來看,childView的frame/bounds/center都沒有改變,但是直覺來看其位置卻改變了。再看一下parentView,隻有bounds改變了,frame和center卻沒變,從直覺來看parentView沒有任何更改。是以很有可能是parentView的bounds修改引起了childView的位置更改。這是為什麼呢?這裡先不說明為什麼,再看一下最常用的UIScrollView:

當滾動視圖的時候,console輸出結果如下:

根據輸出結果可以看到,parentView的center、frame、bounds在滾動過程中都沒有作出更改,但是我們看到的它的位置的确改變了。而對于scrollView來說,其frame和center也沒有更改,但是bounds更改了。

這種現象和上面提到的(例子B)的現象一樣,都是對bounds進行了修改。然後子視圖從新進行了布局。說道子視圖重新布局,讓我想到了一個方法:

從字面意思看就是布局某個視圖的子視圖,那麼會不會和這個方法有關呢?是以我在自定義的ZGUIScrollView裡面實作了該方法:

再次滾動界面,發現每次滾動都會調用scrollview的layoutSubViews方法。蘋果官方文檔介紹:

Lays out subviews. The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews. Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly. You should not call this method directly. If you want to force a layout update, call the setNeedsLayout method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded method.

它的作用就是布局一個視圖上的子視圖。确定子視圖的大小和位置。如果你想強制布局更新,你不能直接去調用這個方法,而是在下次更新圖形之前調用setNeedsLayout方法,如果你要立即更新視圖布局,調用layoutIfNeeded方法。

由此可知,UIScrollView的實作就是通過bounds來實作的。contentOffset是bounds的origin。然後當bounds修改之後,會在layoutSubviews方法裡面對子視圖進行布局。對子類進行更新。

另外,我們還可以用bounds實作如下效果:

深入淺出了解frame和bounds

圖上右側便是使用了bounds實作的效果。實作方式就是在自定義cell中重寫drawReact:

其實UITableView(它是UIScrollView)的實作也是類似,更改了bounds,來實作滾動加載cell。

對bounds和frame的了解就是這些,其實系統用bounds的地方還是很多的。例如UIScrollView的實作就用到了。有疑問的話可以留言交流。

繼續閱讀