iOS 基础:Frames、Bounds 和 CGGeometry
Chinese (Simplified) (中文(简体)) translation by Kang Cheng (you can also view the original English article)
如果你习惯支持点语法的语言,要搞清楚`CGPoint`、`CGSize`和`CGRect`并不难。 不过编程式定位视图或者编写绘图代码一般都很长,因此变得很难读明白。
在这个教程里,我希望能澄清一些对 frames 和 bounds 的误解,并且介绍一下**CGGeometry**,它是一个结构体、常量和功能的集合,能让你更轻松地运用`CGPoint`,`CGSize`和`CGRect`。
1.数据类型
如果你刚开始接触 iOS 或者 OS X 开发,你可能会想`CGPoint`、`CGSize`和`CGRect`到底是什么。 CGGeometry Reference 定义了一系列几何图元(geometric primitives)或者说结构,我们现在关注的是其中的`CGPoint`、`CGSize`和`CGRect`。
大多数人应该知道,`CGPoint`是定义了坐标系中一个点的 C 结构体。 这个坐标系的原点在 iOS 的左上方以及 OS X 的左下方。 换句话说,纵轴方向在 iOS 和 OS X 上不一样。
`CGSize`是另一个简单的 C 结构体,它定义了一个宽度值(width)和高度值(height)。`CGRect`包含一个`origin`(原点)字段、一个`CGPoint`和一个`size`(大小)字段,即一个`CGSize`。 `origin`(原点)和`size`(大小)字段一起决定了一个矩形的位置和大小。
CGGeometry Reference 也定义了其他类型,例如`CGFloat`和`CGVector` 。 `CGFloat`就是一个`float`(单精度浮点型)或者`double`(双精度浮点型)的`typedef`(类型重定义),是哪一种取决于应用运行的机器结构是 32 位还是 64 位。
2.Frames 和 Bounds
第一个要搞清楚的是一个视图的`frame`和`bounds`之间的区别,因为这困扰着很多 iOS 入门开发者。 不过这个区别也不复杂。
在 iOS 和 OS X 中,一个应用有多个坐标系。 比如,在 iOS 中应用窗口定位在屏幕的坐标系,而窗口的每一个子视图定位在窗口的坐标系。 换句话说,一个视图的子视图总是定位在该视图的坐标系中。
Frame
如文档中说的,视图的`frame`是一个结构体,即一个`CGRect`,它定义了这个视图的大小和它在父视图中的位置,或者说父视图坐标系中的位置。 看看下面的图应该就能明白了。



Bounds
视图的`bounds`属性定义了这个视图的大小和它在自身坐标系中的位置。 这意味着大多数情况下一个视图的 bounds 的原点都是`{0,0}`,如下图。 视图的`bounds`对于绘制这个视图很重要。



当视图的`frame`属性被修改时,视图的`center`和`bounds`属性二者或者其一也同时被改变。
3. CGGeometry Reference
方便的取值方法
之前提到过,CGGeometry Reference 是一个让运用坐标和矩形更方便的结构体、常量和方法的集合。 你可能碰到过类似的下面的代码片段:
1 |
CGPoint point = CGPointMake(self.view.frame.origin.x + self.view.frame.size.width, self.view.frame.origin.y + self.view.frame.size.height); |
这样的片段不仅难阅读,而且过于冗长。 我们可以用在 CGGeometry Reference 中定义的两个方便的方法重写这段代码。
1 |
CGRect frame = self.view.frame; |
2 |
CGPoint point = CGPointMake(CGRectGetMaxX(frame), CGRectGetMaxY(frame)); |
为了简化之前那段代码,我们把视图的`frame`储存到一个叫`frame`的变量中,并且使用了`CGRectGetMaxX`和`CGRectGetMaxY`两个方法。 这两个方法的方法名解释了自己的功能。
CGGeometry Reference 定义了返回一个矩形 x 轴坐标、y 轴坐标最小和最大值以及这个矩形中心坐标的方法。 另外两个方便的取之方法是`CGRectGetWidth`和`CGRectGetHeight`。
创建结构体
当要创建`CGPoint`、`CGSize`和`CGRect`时,大多数人都用`CGPointMake`或者类似的方法。 这些方法也被定义在 CGGeometry Reference 中。 虽然它们的实现非常简单,它们特别有用并且让你少写一些代码。 例如,`CGRectMake`是这样实现的:
1 |
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height) |
2 |
{
|
3 |
CGRect rect; |
4 |
rect.origin.x = x; rect.origin.y = y; |
5 |
rect.size.width = width; rect.size.height = height; |
6 |
return rect; |
7 |
}
|
修改矩形
以上提到过的方法都是 iOS 开发者熟知的,它们减少了我们的代码量并且让它们可读性增加。 不过,CGGeometry Reference 也定义了一切其他大家不太了解的方法。 比如 CGGeometry Reference 定义了一堆修改`CGRect`结构的方法。 让我们来看看其中一些。
**`CGRectUnion`**
`CGRectUnion`接受两个`CGRect`结构体作为参数并且返回一个能够包含这两个矩形的最小矩形。 听起来可能没什么,我相信你也可以用几行代码轻松实现这个功能,不过 CGGeometry 做的是给你提供一些方法让你的代码更干净、可读性更强。
如果你把下面代码片段加到一个 view controller 的`viewDidLoad`方法中,你将在模拟器中看到如下结果。 那个灰色的矩形就是使用`CGRectUnion`的结果。
1 |
// CGRectUnion
|
2 |
CGRect frame1 = CGRectMake(80.0, 100.0, 150.0, 240.0); |
3 |
CGRect frame2 = CGRectMake(140.0, 240.0, 120.0, 120.0); |
4 |
CGRect frame3 = CGRectUnion(frame1, frame2); |
5 |
|
6 |
UIView *view1 = [[UIView alloc] initWithFrame:frame1]; |
7 |
[view1 setBackgroundColor:[UIColor redColor]]; |
8 |
|
9 |
UIView *view2 = [[UIView alloc] initWithFrame:frame2]; |
10 |
[view2 setBackgroundColor:[UIColor orangeColor]]; |
11 |
|
12 |
UIView *view3 = [[UIView alloc] initWithFrame:frame3]; |
13 |
[view3 setBackgroundColor:[UIColor grayColor]]; |
14 |
|
15 |
[self.view addSubview:view3]; |
16 |
[self.view addSubview:view2]; |
17 |
[self.view addSubview:view1]; |



**`CGRectDivide`**
另一个有用的方法是`CGRectDivide`,它帮你把一个给定矩形分割成两个。 看看下面的代码和截图来了解它是怎么运作的。
1 |
// CGRectDivide
|
2 |
CGRect frame = CGRectMake(10.0, 50.0, 300.0, 300.0); |
3 |
CGRect part1; |
4 |
CGRect part2; |
5 |
CGRectDivide(frame, &part1, &part2, 100.0, CGRectMaxYEdge); |
6 |
|
7 |
UIView *view1 = [[UIView alloc] initWithFrame:frame]; |
8 |
[view1 setBackgroundColor:[UIColor grayColor]]; |
9 |
|
10 |
UIView *view2 = [[UIView alloc] initWithFrame:part1]; |
11 |
[view2 setBackgroundColor:[UIColor orangeColor]]; |
12 |
|
13 |
UIView *view3 = [[UIView alloc] initWithFrame:part2]; |
14 |
[view3 setBackgroundColor:[UIColor redColor]]; |
15 |
|
16 |
[self.view addSubview:view1]; |
17 |
[self.view addSubview:view2]; |
18 |
[self.view addSubview:view3]; |



如果你不使用`CGRectDivide`来计算红色和橙色矩形的话,你可能要多谢几十行代码。 不信你就试试。
比较和包含
用下面六个方法来比较几何结构和检查包含关系非常简单。
`CGPointEqualToPoint`
`CGSizeEqualToSize`
`CGRectEqualToRect`
`CGRectIntersectsRect`
`CGRectContainsPoint`
`CGRectContainsRect`
CGGeometry Reference 还有一些其他宝贝,比如`CGPointCreateDictionaryRepresentation`可以用来将一个 CGPoint 结构体转换为一个 `CGDictionaryRef`,`CGRectIsEmpty`可以用来检查一个矩形的宽高是否都为零。 更多详情请看[《CGGeometry Reference 文档》](https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CGGeometry/index.html)。
4.福利:打印日志
在 Xcode 控制台打印日志如果没有一些辅助方法的话很麻烦。 幸运的是,UIKit 框架定义了一些让它变得很方便的方法。 我天天用它们。 看看下面的代码片段来了解它们是如何工作。 并没有什么奇特的。
1 |
CGPoint point = CGPointMake(10.0, 25.0); |
2 |
CGSize size = CGSizeMake(103.0, 223.0); |
3 |
CGRect frame = CGRectMake(point.x, point.y, size.width, size.height); |
4 |
NSLog(@"\n%@\n%@\n%@", NSStringFromCGPoint(point), NSStringFromCGSize(size), NSStringFromCGRect(frame)); |
还有一些方便打印仿射变换(affine transforms)(`NSStringFromCGAffineTransform`)、边缘插入(`NSStringFromUIEdgeInsets`)、偏移(`NSStringFromUIOffset`)等的日志的方便方法。
总结
iOS SDK 包含了大量开发者们不了解的宝贝。 我希望我给你们讲明白了 CGGeometry Reference 的实用性。 一旦你开始使用它的那些方法,你就会开始问自己,以前没用它怎么活过来的。