frame
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相對于父視圖坐標(biāo)系的位置和大小,它會影響center屬性和bounds屬性的size。
先看一下它究竟是什么?
它是一個CGRect類型,如下:
struct CGRect {
CGPoint origin;
CGSize size;
};
typedef struct CG_BOXABLE CGRect CGRect;
struct CGPoint {
CGFloat x;
CGFloat y;
};
typedef struct CG_BOXABLE CGPoint CGPoint;
/* Sizes. */
struct CGSize {
CGFloat width;
CGFloat height;
};
typedef struct CG_BOXABLE CGSize CGSize;
其中的origin就是該view的位置,它是一個CGPoint類型,也是一個結(jié)構(gòu)體,包含了我們熟知的常用二維坐標(biāo)系的x、y。根據(jù)x、y可以在坐標(biāo)系里面唯一確定一個點(diǎn)。如下圖:

這個坐標(biāo)系和我們平時接觸的還不太一樣,它是向右向下為正方向。所以對于window來說,其原點(diǎn)是左上角,比如現(xiàn)在的頭像的起始坐標(biāo)就是(200,40)。按照原來常規(guī)的坐標(biāo)系來說,應(yīng)該是(200,-40)。
在設(shè)置一個CGRect的時候,用到的方法是CGRectMake,其實(shí)現(xiàn)如下:
CG_INLINE CGRect
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
CGRect rect;
rect.origin.x = x; rect.origin.y = y;
rect.size.width = width; rect.size.height = height;
return rect;
}
也就是自己在實(shí)現(xiàn)部分創(chuàng)建了一個rect,然后逐個賦值。
關(guān)于frame,這里要注意的一點(diǎn)就是:frame是相對于父視圖的坐標(biāo)系來定位的。如果你這樣設(shè)置frame:(0,0,100,200),也就是在父視圖左上角添加了一個寬100,高200的子視圖(前提是沒有改變父視圖的bounds,接下來會有介紹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.
它也是描述的是視圖的位置和大小,只不過是在自己的坐標(biāo)系上。也就是說它描述的是當(dāng)前視圖相對于自身坐標(biāo)系的位置和大小。
舉個例子:
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect = self.view.frame;
self.parentView = [[ParentView alloc] initWithFrame:CGRectMake(60, 80, rect.size.width-120, rect.size.height - 160)];
self.parentView.backgroundColor = [UIColor redColor];
[self.view addSubview:_parentView];
NSLog(@"frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"bounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"center:%@",NSStringFromCGPoint(self.parentView.center));
}
輸出的結(jié)果如下:
frame:{{60, 80}, {200, 408}}
bounds:{{0, 0}, {200, 408}}
center:{160, 284}
由此可見,如果我們沒有去更改bounds的值,它默認(rèn)的位置坐標(biāo)點(diǎn)是(0,0)。
center
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.
Changes to this property can be animated.
center是view的中點(diǎn)。該屬性是想歸于父類的坐標(biāo)系確定的。從bounds小節(jié)里面的例子可以看到center的值,其計算方法為:
center.x = frame.origin.x + frame.size.width/2
center.y = frame.origin.y + frame.size.height/2
transform
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.
Changes to this property can be animated.
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.
它用于指定視圖的變換。使用這個屬性可以放大或者旋轉(zhuǎn)視圖,它的frame會因此改變,是以中心點(diǎn)為變換的??蠢樱?/p>
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect = self.view.frame;
self.parentView = [[ParentView alloc] initWithFrame:CGRectMake(60, 80, rect.size.width-120, rect.size.height - 160)];
self.parentView.backgroundColor = [UIColor redColor];
[self.view addSubview:_parentView];
NSLog(@"frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"bounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"center:%@",NSStringFromCGPoint(self.parentView.center));
self.parentView.transform = CGAffineTransformMakeRotation(60);
NSLog(@"after change transform,frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"after change transform,bounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"after change transform,center:%@",NSStringFromCGPoint(self.parentView.center));
}
看輸出的結(jié)果:
frame:{{60, 80}, {200, 408}}
bounds:{{0, 0}, {200, 408}}
center:{160, 284}
after change transform,frame:{{2.5773352536321568, 59.226689885086444}, {314.84532949273569, 449.54662022982711}}
after change transform,bounds:{{0, 0}, {200, 408}}
after change transform,center:{160, 284}
如圖:

可以看出,當(dāng)我們對圖像通過旋轉(zhuǎn),旋轉(zhuǎn)后的圖片的frame已經(jīng)變成了{(lán)(2.5773352536321568, 59.226689885086444), (314.84532949273569, 449.54662022982711)},此時的起始位置為圖上旋轉(zhuǎn)后標(biāo)的(2.58,59.2),大小也變成了雙箭頭黑線標(biāo)注的大小。
因此得出結(jié)論:進(jìn)行了transform變換,其frame改變了,但是其bounds和center并沒有修改。此時bounds的size和frame的size已經(jīng)沒有關(guān)系了。當(dāng)沒有進(jìn)行任何transform時,frame的size總是和bounds相等。
以上便是對frame、bounds、center和transform做了一個簡單的介紹。
bounds的使用
接下來看一個例子(例子A):
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect = self.view.frame;
self.parentView = [[ParentView alloc] initWithFrame:CGRectMake(60, 80, rect.size.width-120, rect.size.height - 160)];
self.parentView.backgroundColor = [UIColor redColor];
[self.view addSubview:_parentView];
NSLog(@"frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"bounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"center:%@",NSStringFromCGPoint(self.parentView.center));
self.parentView.bounds = CGRectMake(-40, -40, self.parentView.frame.size.width, self.parentView.frame.size.height);
NSLog(@"parent change bound ,frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"parent change bound ,bounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"parent change bound ,center:%@",NSStringFromCGPoint(self.parentView.center));
self.childView = [[ChildView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)];
self.childView.backgroundColor = [UIColor yellowColor];
[self.parentView addSubview:_childView];
NSLog(@"childView frame:%@",NSStringFromCGRect(self.childView.frame));
NSLog(@"childView ounds:%@",NSStringFromCGRect(self.childView.bounds));
NSLog(@"childView center:%@",NSStringFromCGPoint(self.childView.center));
}
這里在parentView上添加了一個childView,然后對parentView的bounds進(jìn)行修改和不修改進(jìn)行了測試,結(jié)果如下:

你會發(fā)現(xiàn)當(dāng)修改了parentView的bounds之后,發(fā)現(xiàn)childView缺向右向下做了偏移。這里設(shè)置parentView的bounds的origin為(-40,-40)為何會發(fā)生這種情況呢?接下來先看一下下面這張圖:

+代表正方向,-代表負(fù)方向。
如果此時我們沒有改變圖中O的坐標(biāo),那么此時A的坐標(biāo)是(20,20),如果我們更改了O的坐標(biāo)為(-20,-20),那么原來A點(diǎn)的坐標(biāo)就成了A'(0,0),但是A坐標(biāo)是不變的,所以它會到黑色A處。所以你改變了原點(diǎn)坐標(biāo)為負(fù)之后,A點(diǎn)會移動到黑色A。相反如果你設(shè)置了坐標(biāo)原點(diǎn)為(20,20),那么A點(diǎn)就會和坐標(biāo)原點(diǎn)重合。
這就是為什么childView會向右向下移動的原因。
接下來再做如下操作(例子B):
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect = self.view.frame;
self.parentView = [[ParentView alloc] initWithFrame:CGRectMake(60, 80, rect.size.width-120, rect.size.height - 160)];
self.parentView.backgroundColor = [UIColor redColor];
[self.view addSubview:_parentView];
NSLog(@"parent change bound ,frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"parent change bound ,bounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"parent change bound ,center:%@",NSStringFromCGPoint(self.parentView.center));
self.childView = [[ChildView alloc] initWithFrame:CGRectMake(self.parentView.frame.origin.x, self.parentView.frame.origin.y+self.parentView.frame.size.height-200, 100, 100)];
self.childView.backgroundColor = [UIColor yellowColor];
[self.parentView addSubview:_childView];
NSLog(@"childView frame:%@",NSStringFromCGRect(self.childView.frame));
NSLog(@"childView ounds:%@",NSStringFromCGRect(self.childView.bounds));
NSLog(@"childView center:%@",NSStringFromCGPoint(self.childView.center));
NSLog(@"\n--------\n");
CGRect parentBounds = self.parentView.bounds;
[UIView animateWithDuration:2 animations:^{
self.parentView.bounds = CGRectMake(parentBounds.origin.x, 400, parentBounds.size.width, parentBounds.size.height);
} completion:^(BOOL finished) {
NSLog(@"anim finished,parentView frame:%@",NSStringFromCGRect(self.parentView.frame));
NSLog(@"anim finished,parentView ounds:%@",NSStringFromCGRect(self.parentView.bounds));
NSLog(@"anim finished,parentView center:%@",NSStringFromCGPoint(self.parentView.center));
NSLog(@"anim finished,childView frame:%@",NSStringFromCGRect(self.childView.frame));
NSLog(@"anim finished,childView bounds:%@",NSStringFromCGRect(self.childView.bounds));
NSLog(@"anim finished,childView center:%@",NSStringFromCGPoint(self.childView.center));
}];
}
輸出結(jié)果如下:
parent change bound ,frame:{{60, 80}, {200, 408}}
parent change bound ,bounds:{{0, 0}, {200, 408}}
parent change bound ,center:{160, 284}
childView frame:{{60, 288}, {100, 100}}
childView ounds:{{0, 0}, {100, 100}}
childView center:{110, 338}
--------
anim finished,parentView frame:{{60, 80}, {200, 408}}
anim finished,parentView ounds:{{0, 400}, {200, 408}}
anim finished,parentView center:{160, 284}
anim finished,childView frame:{{60, 288}, {100, 100}}
anim finished,childView bounds:{{0, 0}, {100, 100}}
anim finished,childView center:{110, 338}
運(yùn)行效果是childView向上移動,然后停止。結(jié)果前后對比圖如下:

直觀來看,按說childView的frame改變了,但是從console輸出的結(jié)果來看,childView的frame/bounds/center都沒有改變,但是直觀來看其位置卻改變了。再看一下parentView,只有bounds改變了,frame和center卻沒變,從直觀來看parentView沒有任何更改。所以很有可能是parentView的bounds修改引起了childView的位置更改。這是為什么呢?這里先不說明為什么,再看一下最常用的UIScrollView:
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[ZGUIScrolLView alloc] initWithFrame:self.view.frame];
self.scrollView.delegate = self;
[self.view addSubview:_scrollView];
NSLog(@"scrollview frame:%@",NSStringFromCGRect(_scrollView.frame));
NSLog(@"scrollview bounds:%@",NSStringFromCGRect(_scrollView.bounds));
NSLog(@"scrollview center:%@",NSStringFromCGPoint(_scrollView.center));
self.scrollView.contentSize = CGSizeMake(800, 800);
self.parentView = [[ParentView alloc] initWithFrame:CGRectMake(20, 100, 250, 300)];
self.parentView.backgroundColor = [UIColor redColor];
[self.scrollView addSubview:_parentView];
NSLog(@"parentView frame:%@",NSStringFromCGRect(_parentView.frame));
NSLog(@"parentView bounds:%@",NSStringFromCGRect(_parentView.bounds));
NSLog(@"parentView center:%@",NSStringFromCGPoint(_parentView.center));
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(@"didScroll scrollview frame:%@",NSStringFromCGRect(_scrollView.frame));
NSLog(@"didScroll scrollview bounds:%@",NSStringFromCGRect(_scrollView.bounds));
NSLog(@"didScroll scrollview center:%@",NSStringFromCGPoint(_scrollView.center));
NSLog(@"didScroll parentView frame:%@",NSStringFromCGRect(_parentView.frame));
NSLog(@"didScroll parentView bounds:%@",NSStringFromCGRect(_parentView.bounds));
NSLog(@"didScroll parentView center:%@",NSStringFromCGPoint(_parentView.center));
printf("\n-------------------------------------------\n");
}
當(dāng)滾動視圖的時候,console輸出結(jié)果如下:
scrollview frame:{{0, 0}, {320, 568}}
scrollview bounds:{{0, 0}, {320, 568}}
scrollview center:{160, 284}
parentView frame:{{20, 100}, {250, 300}}
parentView bounds:{{0, 0}, {250, 300}}
parentView center:{145, 250}
didScroll scrollview frame:{{0, 0}, {320, 568}}
didScroll scrollview bounds:{{0, -20}, {320, 568}}
didScroll scrollview center:{160, 284}
didScroll parentView frame:{{20, 100}, {250, 300}}
didScroll parentView bounds:{{0, 0}, {250, 300}}
didScroll parentView center:{145, 250}
-------------------------------------------
didScroll scrollview frame:{{0, 0}, {320, 568}}
didScroll scrollview bounds:{{8.5, 31.5}, {320, 568}}
didScroll scrollview center:{160, 284}
didScroll parentView frame:{{20, 100}, {250, 300}}
didScroll parentView bounds:{{0, 0}, {250, 300}}
didScroll parentView center:{145, 250}
-------------------------------------------
didScroll scrollview frame:{{0, 0}, {320, 568}}
didScroll scrollview bounds:{{25.5, 162}, {320, 568}}
didScroll scrollview center:{160, 284}
didScroll parentView frame:{{20, 100}, {250, 300}}
didScroll parentView bounds:{{0, 0}, {250, 300}}
didScroll parentView center:{145, 250}
根據(jù)輸出結(jié)果可以看到,parentView的center、frame、bounds在滾動過程中都沒有作出更改,但是我們看到的它的位置的確改變了。而對于scrollView來說,其frame和center也沒有更改,但是bounds更改了。
這種現(xiàn)象和上面提到的(例子B)的現(xiàn)象一樣,都是對bounds進(jìn)行了修改。然后子視圖從新進(jìn)行了布局。說道子視圖重新布局,讓我想到了一個方法:
- (void)layoutSubviews;
從字面意思看就是布局某個視圖的子視圖,那么會不會和這個方法有關(guān)呢?因此我在自定義的ZGUIScrollView里面實(shí)現(xiàn)了該方法:
- (void)layoutSubviews {
NSLog(@"scrollview's layoutSubViews called");
[super layoutSubviews];
}
再次滾動界面,發(fā)現(xiàn)每次滾動都會調(diào)用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.
它的作用就是布局一個視圖上的子視圖。確定子視圖的大小和位置。如果你想強(qiáng)制布局更新,你不能直接去調(diào)用這個方法,而是在下次更新圖形之前調(diào)用setNeedsLayout方法,如果你要立即更新視圖布局,調(diào)用layoutIfNeeded方法。
由此可知,UIScrollView的實(shí)現(xiàn)就是通過bounds來實(shí)現(xiàn)的。contentOffset是bounds的origin。然后當(dāng)bounds修改之后,會在layoutSubviews方法里面對子視圖進(jìn)行布局。對子類進(jìn)行更新。
另外,我們還可以用bounds實(shí)現(xiàn)如下效果:

圖上右側(cè)便是使用了bounds實(shí)現(xiàn)的效果。實(shí)現(xiàn)方式就是在自定義cell中重寫drawReact:
- (void)drawRect:(CGRect)rect {
self.bounds = CGRectMake(self.bounds.origin.x, self.bounds.origin.y, self.frame.size.width-20, self.frame.size.height - 5);
[super drawRect:rect];
}
其實(shí)UITableView(它是UIScrollView)的實(shí)現(xiàn)也是類似,更改了bounds,來實(shí)現(xiàn)滾動加載cell。
總結(jié)
對bounds和frame的理解就是這些,其實(shí)系統(tǒng)用bounds的地方還是很多的。例如UIScrollView的實(shí)現(xiàn)就用到了。有疑問的話可以留言交流。
注:轉(zhuǎn)贊請標(biāo)明來源:張貴的博客