首页

时间和精神的房子
壹只iOS程序员的修行世界,欢迎来访

如果文章对您有所帮助
将是我最大的荣幸

使用CGGeometry方法获取CGRect数据

CGGeometry 中提供了取特定 CGRect 值的便捷方法。

  • CGRectGetMinX
  • CGRectGetMinY
  • CGRectGetMidX
  • CGRectGetMidY
  • CGRectGetMaxX
  • CGRectGetMaxY
  • CGRectGetWidth
  • CGRectGetHeight

这其中CGRectGetMidX,CGRectGetMidY,CGRectGetMaxX,CGRectGetMaxY四个方法十分有用。用CGRectGetMaxX代替frame.origin.x + frame.size.width将使代码更加清晰、语义上也更为生动直观。另外三个方法也同样,他们都可以用来代替类似这样的代码。

  • CGRectGetMidX用来替代frame.origin.x + frame.size.width / 2
  • CGRectGetMidY用来替代frame.origin.y + frame.size.height / 2
  • CGRectGetMaxX用来替代frame.origin.x + frame.size.width
  • CGRectGetMaxY用来替代frame.origin.y + frame.size.height

这时你也许会想到,那CGRectGetMinX,CGRectGetMinY,CGRectGetWidth,CGRectGetHeight四个方法是不是也是用来代替类似这样的代码。

  • CGRectGetMinX用来替代frame.origin.x
  • CGRectGetMinY用来替代frame.origin.y
  • CGRectGetWidth用来替代frame.size.width
  • CGRectGetHeight用来替代frame.size.width

答案是肯定的。但这四个方法看起来却不是那么有必要。因为跟直接调用相比,这样写代码要更长。

  • CGRectGetMinX(frame)
  • frame.origin.x

而且如果想要唤起 Xcode 的联想,最少需要输入“CGRectGet”八个字母。而直接调用通常只需要输入“f”,“.o”,“.x”五个字母就能联想完成。经过性能测试,CGRectGetMinX(frame) 的亿次调用耗时561毫秒,而直接调用由于不明原因,不管循环多少次测试调用时间都低于1毫秒。竟然代码更长,输入更慢,效率更低,那 CGGeometry 提供这些接口的意义在哪呢。其实,仅凭代码统一的代码风格,生动直观的语义也值得你这样去做。而且效率上的差异只是理论数值,实际上CGRectGet的10万次调用只耗时1毫秒,在整个APP的运行期间也很难有超过10万次的调用。

苹果官方文档推荐使用 CGGeometry 方法,同时尽量避免直接调用。事实上仅仅因为上述的这些好处苹果是不会建议尽量避免直接调用的,这两种调用方法其实存在这本质的差异。在平时的使用中我们没有遇到差异,是因为我们使用的 frame 或 bounds 通常都是正数,当负数出现的时候,有意思的事情就来了。

StackOverflow摘自苹果邮件列表的原文说明了这个现象。大意就是“CGRectGetWidth/Height 会将 width 或者 height 格式化,格式化基本上只是检查 width 或者 height 是否为负数,如果为负数则取绝对值”。下边就来解释这个现象背后的原因和他们本质的差别。

CGRectGetWidth/Height will normalize the width or height before returning them. Normalization is basically just checking if the width or height is negative, and negating it to make it positive if so.

CGRect 是用来储存矩形几何信息的容器,通过这些信息就能知道怎么去绘制矩形。UIView 通过 CGRect 类型的成员变量 frame 来描述自己的位置和形状。通过 CGRectGet 方法获取到的是 view 的实际位置和形状,而直接调用获取的只是描述几何体位置和形状的信息,他们的是带有方向性的。比如,view 的宽是-100,代表的是在 x 轴反方向的100,最终绘制出来矩形的宽还是100,只是在 x 轴负方向而已。所以,通过 CGRectGetWidth(frame) 获取到的就是真实的宽度100,而直接调用只能获取同时描述宽度和方向的数值-100。这看起来只是取绝对值这么简单,但其他方法可并不是这样。通过如下代码绘制出来下图所示的两个 view 将帮助你更好的理解这一点。

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(200, 500, 100, 100)];
view.backgroundColor = [UIColor grayColor];
[self.view addSubview:view];

UIView *subview = [[UIView alloc] initWithFrame:CGRectZero];
subview.backgroundColor = [UIColor lightGrayColor];
[view addSubview:subview];

CGRect frame = CGRectMake(-50, -100, -100, -150);
NSLog(@"%f, %f", CGRectGetMinX(frame), frame.origin.x);
NSLog(@"%f, %f", CGRectGetMinY(frame), frame.origin.y);
NSLog(@"%f, %f", CGRectGetMidX(frame), frame.origin.x + frame.size.width / 2);
NSLog(@"%f, %f", CGRectGetMidY(frame), frame.origin.y + frame.size.height / 2);
NSLog(@"%f, %f", CGRectGetMaxX(frame), frame.origin.x + frame.size.width);
NSLog(@"%f, %f", CGRectGetMaxY(frame), frame.origin.y + frame.size.height);
NSLog(@"%f, %f", CGRectGetWidth(frame), frame.size.width);
NSLog(@"%f, %f", CGRectGetHeight(frame), frame.size.height);

subview.frame = frame;

通过图片显而易见,使用 CGRectGetMinX 获取到的是 viewB 显示在屏幕上最左边位置,就是-150。而通过 frame.origin.x 获取到的必然是-50。简单来说,只要 width 或者 height 是负数,那么这个 view 的左右或者上下其实是翻转过的。你之前用直接调用的方法(frame.origin.x + frame.size.width)去获取的(-50 - 100 = -150)其实 view 左边。

代码打印出来 CGRectGet 方法的数据都符合这样的规则。

2016-02-26 15:40:13.141 LPTest[2319:859916] -150.000000, -50.000000
2016-02-26 15:40:13.141 LPTest[2319:859916] -250.000000, -100.000000
2016-02-26 15:40:13.141 LPTest[2319:859916] -100.000000, -100.000000
2016-02-26 15:40:13.141 LPTest[2319:859916] -175.000000, -175.000000
2016-02-26 15:40:13.141 LPTest[2319:859916] -50.000000, -150.000000
2016-02-26 15:40:13.141 LPTest[2319:859916] -100.000000, -250.000000
2016-02-26 15:40:13.142 LPTest[2319:859916] 100.000000, -100.000000
2016-02-26 15:40:13.142 LPTest[2319:859916] 150.000000, -150.00000

通过这样的规则,我们推理出这八种方法的函数原型。如下所示。

CG_EXTERN CGFloat CGRectGetMinX(CGRect rect) {
    return rect.origin.x + (rect.size.width > 0? 0: rect.size.width);
}

CG_EXTERN CGFloat CGRectGetMinY(CGRect rect) {
    return rect.origin.y + (rect.size.height > 0? 0: rect.size.height);
}

CG_EXTERN CGFloat CGRectGetMidX(CGRect rect) {
    return rect.origin.x + rect.size.width / 2;
}

CG_EXTERN CGFloat CGRectGetMidY(CGRect rect) {
    return rect.origin.y + rect.size.height / 2;
}

CG_EXTERN CGFloat CGRectGetMaxX(CGRect rect) {
    return rect.origin.x + (rect.size.width < 0? 0: rect.size.width);
}

CG_EXTERN CGFloat CGRectGetMaxY(CGRect rect) {
    return rect.origin.y + (rect.size.height < 0? 0: rect.size.height);
}

CG_EXTERN CGFloat CGRectGetWidth(CGRect rect) {
    return fabs(rect.size.width);
}

CG_EXTERN CGFloat CGRectGetHeight(CGRect rect) {
    return fabs(rect.size.height);
}

大部分情况下,我们调用 frame.origin.x 的目的其实都是想获取 view 最左边的位置,其实都应该调用 CGRectGetMinX(frame) 方法。也许你以前习惯通过 frame.origin.x 来获取左边的位置,通过 CGRectGetMaxX(frame) 来获取右边的位置。这样混合调用的代码是存在很多隐患的,只是可能并没有体现出来。在这种情况你最好还是改正下自己的习惯。了解了这些后你就能根据使用场景来决定自己应该怎么做。最后,对 CGRectGet 方法的好处进行简单总结。

  1. 语义生动直观
  2. 代码风格统一
  3. 能够获取到view真实的位置
  4. 调用接口简单

参考资料

关注作者

分享本文

目录