CodeV

2.3-主要的结构体

iOS绘图使用四个关键结构体来定义几何图元:points,sizes,rectangles和transforms。这些结构体都使用共同的单位,逻辑点。点是使用CGFloat值定义的。这些类型定义为iOS上的float和OS X上的double。

与固有整数的像素不同,点不绑定到设备硬件。它们的值涉及数学坐标,提供子像素精度。iOS绘图系统代替您处理其中的数学运算。

您使用的四个基元如下:

  • CGPoint - Points结构由xy坐标组成。它们定义逻辑位置。
  • CGSize - Size结构具有widthheight。他们确定宽高。
  • CGRect - Rectangles包括由点定义的originsize
  • CGAffineTransform - 仿射变换结构体描述应用于几何项目的更改 - 特别是项目如何放置,缩放和旋转。它将abcdtxty值存储在定义特定变换的矩阵中。

接下来的部分将更深入地介绍这些项目。在深入了解绘图的细节之前,您需要掌握这些基础的几何知识。

Points

CGPoint结构体存储一个逻辑位置,您通过向xy字段分配值来定义该逻辑位置。有一个非常方便的函数:CGPointMake()可以从您传递的两个参数来构建点结构体:

1
2
3
4
struct CGPoint {
CGFloat x;
CGFloat y;
};

这些值可以存储任何浮点数,负值与正值一样有效。

Sizes

CGSize结构体存储两个延伸的值,widthheight。 使用CGSizeMake()来构建大小。 高度和宽度可以是在日常实践中不常见的负值也可以是常见的正值:

1
2
3
4
struct CGSize {
CGFloat width;
CGFloat height;
};

Rectangles

CGRect结构体由两个子结构体组成:CGPoint(用于定义矩形的原点)和CGSize(用于定义其范围)。CGRectMake()函数接受四个参数并返回一个填充的rect结构体。按顺序,参数是x原点,y原点,宽度和高度。 例如,CGRectMake(0,0,50,100)

1
2
3
4
struct CGRect {
CGPoint origin;
CGSize size;
};

调用CGRectStandardize()将带有负数扩展区的矩形转换为等价形式版本的正宽度和高度。

Transforms

变换是体现iOS几何的最强大的方面之一。它们允许在一个坐标系中的点变换到另一个坐标系。它们允许你缩放,旋转,镜像和平移你绘制的图形元素,同时保持线性和相对比例。主要是在调整绘图上下文时会遇到绘图变换,就像你在第1章中所看到的那样,或者如你将在第5章中阅读到的当你操作路径(形状)的时候。

广泛应用于2D和3D图形,变换为许多几何解决方案提供了一个复杂的机制。Core Graphics版本(CGAffineTransform)使用3乘3矩阵,使其成为仅限2D的解决方案。使用4乘4矩阵的3D变换是Core Animation图层的默认值。Quartz变换使您能够缩放,平移和旋转几何。

每个变换由基础变换矩阵表示,其设置如下:

图2.3

该矩阵对应于简单的C结构体:

1
2
3
4
5
6
7
8
struct CGAffineTransform {
CGFloat a;
CGFloat b;
CGFloat c;
CGFloat d;
CGFloat tx;
CGFloat ty;
};

Creating Transforms

与其他Core Graphics结构不同,很少直接访问仿射变换的字段。大多数人不会使用或需要CGAffineTransformMake()构造函数,它将六个组件中的每一个都作为参数。

相反,创建变换的典型入口点是CGAffineTransformMakeScale()CGAffineTransformMakeRotation()CGAffineTransformMakeTranslation()。 这些函数根据您提供的参数构建缩放,旋转和平移(位置偏移)矩阵。通过使用这些选项,您可以在要应用它们的术语中讨论要应用的操作。需要旋转对象吗?指定旋转度数。要移动对象?以点数表示偏移量。每个函数创建一个变换,在应用时会生成您指定的操作。

Layering Transforms

相关的函数使您能够将一个转换叠加到另一个转换,从而提高复杂性。与第一组函数不同,这些函数将变换作为参数。他们修改该变换,以在顶部执行另一个操作。与“make”函数不同,这些函数总是按顺序应用,每个函数的结果传递给下一个。例如,您可以创建一个旋转和缩放围绕其中心的对象的变换。 以下代码段演示了如何执行此操作:

1
2
3
4
5
CGAffineTransform t = CGAffineTransformIdentity;
t = CGAffineTransformTranslate(t, -center.x, -center.y);
t = CGAffineTransformRotate(t, M_PI_4);
t = CGAffineTransformScale(t, 1.5, 1.5);
t = CGAffineTransformTranslate(t, center.x, center.y);

这里从身份(原始)转换(CGAffineTransformIdentity)开始。恒等变换是加法中的数字0或乘法中的数字1的仿射等效物。当应用于任何几何对象时,它返回与您开始的相同的表示。在此使用它以确保以后操作的一致起点。

因为变换是从它们的原点应用的,而不是它们的中心,所以你可以将中心平移到原点。缩放和旋转变换始终与(0,0)相关。如果要将它们与另一个点(例如视图的中心或路径)相关联,则应始终将该点移动到(0,0)。 在那里,您可以在将原点复位回其初始位置之前执行旋转和缩放。这整个操作集合存储在单个最终变换t中。

Exposing Transforms

UIKit框架定义了特定于图形和绘图操作的各种辅助函数。这些包括几个仿射特定的函数。您可以通过UIKit的NSStringFromCGAffineTransform()函数打印视图的变换。它的逆(函数)是CGAffineTransformFromString()。以下是当记录缩放1.5倍并旋转45度的变换时的值:

1
2013-03-31 09:43:20.837 HelloWorld[41450:c07] [1.06066, 1.06066, -1.06066, 1.06066, 0, 0]

这个特定的变换跳过了任何中心重定向,所以你只关注这两个操作。

这些原始数字不是特别有意义。具体来说,此表示法不直接告诉您变换缩放或旋转多少。幸运的是,有一个简单的方法,一种将非直观的参数转换为更有用(更直观的数值)的表示方法。清单2-1显示了如何实现。它计算x和y刻度值以及旋转角度,从变换结构体的组件返回这些值。

无需计算平移(位置偏移)值。这些值直接存储在tx和ty字段中,本质上是“纯文本”。

清单 2-1 从变换中提取缩放和旋转值信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Extract the x scale from transform
CGFloat TransformGetXScale(CGAffineTransform t)
{
return sqrt(t.a * t.a + t.c * t.c);
}

// Extract the y scale from transform
CGFloat TransformGetYScale(CGAffineTransform t)
{
return sqrt(t.b * t.b + t.d * t.d);
}

// Extract the rotation in radians
CGFloat TransformGetRotation(CGAffineTransform t)
{
return atan2f(t.b, t.a);
}

Predefined Constants

每个Core Graphics结构体都有预定义的常量。使用Quartz时,你会遇到的最常见的如下:

  • Geometric zeros - 这些常量使用零值提供默认值。CGPointZero是一个指向位置(0,0)的点常量。CGSizeZero常量引用其宽度和高度都为0。CGRectZero等效于CGRectMake(0,0,0,0),零原点和大小。
  • Geometric identity - CGAffineTransformIdentity提供常规identity转换。当应用于任何几何元素时,此变换将返回您开始的同一元素。
  • Geometric infinity - CGRectInfinite是一个有无限范围的矩形。宽度和高度设置为CGFLOAT_MAX,最大的浮点值。原点设置为可能的最小负数(-CGFLOAT_MAX / 2)。
  • Geometric nulls - CGRectNullCGRectZero不同之处在于它没有位置。CGRectZero的位置是(0,0)。CGRectNull的位置是(INFINITYINFINITY)。 当你请求两个不相交的矩形的交集时,你会遇到这种矩形。
    任何矩形与CGRectNull的联合(并集)总是返回原来的rect。例如,CGRectMake(10,10,10,10)与CGRectNull的并集为{10,10,10,10}。与CGRectZero的联合(并集)(结果是{0,0,20,20})进行对比。原点被拉向(0,0),使矩形的尺寸加倍并改变其位置。

Conversion to Objects

因为几何项目是结构体而不是对象,将它们集成到标准Objective-C中可能是有挑战性的。您不能将size结构体或rectangle结构体添加到NSArray或字典。不能为点或变换设置默认值。因此,Core Graphics和Core Foundation提供了函数和类来转换和封装对象中的结构体。几何结构的最常见的对象解决方案是字符串(strings),字典(dictionaries)和值(values)。

Strings

通过使用少数转换函数将结构体转换为字符串表示形式,这些函数列在表2-2中。这些便利功能使您能够以人类可读格式记录日志信息或使用定义良好且易于恢复的模式将结构体存储到文件中。转换后的点或大小(例如,NSStringFromCGPoint(CGPointMake(5,2)))看起来像这样:{5,2}。

字符串对于将信息记录到调试控制台是最有用的。

表格 2-2 字符串实用函数

Type Convert to String Convert from String
CGPoint NSStringFromCGPoint() CGPointFromString()
CGSize NSStringFromCGSize() CGSizeFromString()
CGRect NSStringFromCGRect() CGRectFromString()
CGAffineTransform NSStringFromCGAffineTransform() CGAffineTransformFromString()

Dictionaries

字典提供了另一种方式将几何结构体转换为用于存储和记录的对象。如表2-3所示,这些仅限于points,sizes和rectangles。此外,他们返回Core Foundation CFDictionaryRef实例,而不是NSDictionary项。你需要手动桥接这个结果。

字典对于将结构体存储为用户默认最有用。

表格 2-3 字典实用函数

Type Convert to Dictionary Convert from Dictionary
CGPoint CGPointCreateDictionaryRepresentation () CGPointMakeWithDictionaryRepresentation ()
CGSize CGSizeCreateDictionaryRepresentation() CGSizeMakeWithDictionaryRepresentation()
CGRect CGRectCreateDictionaryRepresentation() CGRectMakeWithDictionaryRepresentation()

以下代码将矩形转换为Cocoa Touch字典表示,然后返回给CGRect,并记录结果:

1
2
3
4
5
6
7
8
9
10
// Convert CGRect to NSDictionary representation
CGRect testRect = CGRectMake(1, 2, 3, 4);
NSDictionary *dict = (__bridge_transfer NSDictionary *)CGRectCreateDictionaryRepresentation(testRect);
NSLog(@"Dictionary: %@", dict);

// Convert NSDictionary representation to CGRect
CGRect outputRect;
BOOL success = CGRectMakeWithDictionaryRepresentation((__bridge CFDictionaryRef) dict, &outputRect);
if (success)
NSLog(@"Rect: %@", NSStringFromCGRect(outputRect));

这里是这段代码产生的输出。在这里,您将看到测试矩形的字典表示和在转换回CGRect后恢复的矩形:

1
2
3
4
5
6
7
2013-04-02 08:23:07.323 HelloWorld[62600:c07] Dictionary: {
Height = 4;
Width = 3;
X=1;
Y=2;
}
2013-04-02 08:23:07.323 HelloWorld[62600:c07] Rect: {{1, 2}, {3, 4}}

Note

CGRectMakeWithDictionaryRepresentation()函数有一个已知的错误,使得一些在OS X上创建的值在iOS上不可读。

Values

NSValue类为C数据项提供了一个容器。它可以保存标量类型(如整数和浮点数),指针和结构体。 UIKit扩展了正常的NSValue行为以封装Core Graphics原语。当一个标量类型被放入一个值中,你可以像对待任何其他对象一样添加几何基元到Objective-C集合中。

值对于向应用内计算的数组和字典添加结构体最有用。

表格 2-4 值方法

Type Place in NSValue Retrieve from NSValue
CGPoint + valueWithCGPoint: - CGPointValue
CGSize + valueWithCGSize: - CGSizeValue
CGRect + valueWithCGRect: - CGRectValue
CGAffineTransform + valueWithCGAffineTransform: - CGAffineTransformValue

Note

NSCoderCGPointCGSizeCGRectCGAffineTransform提供UIKit特定的几何编码和解码的方法。这些方法使您能够存储和恢复这些几何结构,将它们与您指定的键相关联。

Geometry Tests

Core Graphics提供了一组基本的几何测试函数,您可以在表2-5中看到这些函数。这些项目使您能够彼此比较项目,并检查可能遇到的特殊情况。这些函数命名同字面的解释一样。

表格 2-5 Checking Geometry

Topic Functions
Special conditions CGRectIsEmpty(rect) CGRectIsNull(rect) CGRectIsInfinite(rect) CGAffineTransformIsIdentity(transform)
Equality CGPointEqualToPoint(p1, p2) CGSizeEqualToSize(s1, s2) CGRectEqualToRect(r1, r2) CGAffineTransformEqualToTransform(t1, t2)
Relationships CGRectContainsPoint(rect, point) CGRectContainsRect(r1, r2) CGRectIntersectsRect(r1, r2)

Other Essential Functions

这里有一些你想知道的最终的函数:

  • CGRectInset(rect,xinset,yinset) - 此函数使您能够创建一个较小或较大的矩形,其中心在与源矩形在相同的点。对于较小的矩形使用正的inset,对较大的矩形使用负的inset。此功能对于将绘图和子图像从视图边缘移开以提供空白(边框)特别有用。
  • CGRectOffset(rect,xoffset,yoffset) - 此函数返回一个矩形,它从原始矩形偏移一个你指定的x和y量。偏移是方便移动frames和创建简单的阴影效果。
  • CGRectGetMidX(rect)和CGRectGetMidY(rect) - 这些函数返回矩形中心的x和y坐标。 这些函数使得获取bounds和frames的中心点非常方便。相关函数返回minXmaxXminYmaxYwidthheight中心点可以帮助绘制图形中的项目。
  • CGRectUnion(rect1,rect2) - 此函数返回完全包含两个源矩形的最小矩形(并集)。此函数可帮助您为正在绘制的不同元素建立最小边界框。绘图路径的联合(并集)允许您将frame项目组织在一起,并为这些项目构建backsplash。
  • CGRectIntersection(rect1,rect2) - 此函数返回两个矩形的交集,如果两个矩形不相交,则返回CGRectNullIntersections帮助你在使用自动布局时计算矩形的insets。路径bounds和图像frame之间的相交可以定义用于对齐的内在内容。
  • CGRectIntegral(rect) - 此函数将源矩形的值转换为整数。origin值从任何小数值向下取整为整数。size值则向上取整。以保证新的矩形完全包含原始的矩形。Integral rectangles加速你的绘图和使你的绘图更清晰。完全在像素边界上绘制的视图需要更少的抗锯齿,并能导致更少的模糊输出。
  • CGRectStandardize(rect) - 此函数返回具有正高度和宽度值的等效矩形。当执行交互式绘图时,标准化的矩形可以简化数学运算,特别是当用户可以交互式地向左和向上移动而不是向右和向下移动时。
  • CGRectDivide(rect,&sliceRect,&residualRect,amount,edge) - 这个函数是这些Core Graphics函数中最复杂的,但它也是最有用的。Division使您能够将矩形重复切分为多个部分,因此可以细分绘图区域。

本文翻译自《iOS Drawing Practical UIKit Solutions》作者:Erica Sadun,翻译:Cheng Dong。如果觉得本书不错请购买支持正版:亚马逊购买传送门,本书所有源代码可在GitHub上下载。译者虽然力求做到信,达,雅,但是由于时间仓促加之译者水平十分有限,文中难免会出现不正确,不准确,词不达意,难于理解的地方,还望各位批评指正,共同进步,谢谢。转载请注明出处。