0%

Drawing and Printing Guide for iOS

本文是对官方文档Drawing and Printing Guide for iOS的一个翻译学习记录。

About Drawing and Printing in iOS

本文档覆盖三个相关的部分:

  • 绘制自定义 UI 视图。 (自定义 UI 视图允许绘制无法使用标准UI元素轻松绘制的内容。 例如,绘图程序可能会为用户的绘图使用自定义视图,或者街机游戏可能会使用自定义视图来绘制精灵。)
  • 绘制到屏幕外的位图和 PDF 内容。 (Drawing into offscreen bitmap and PDF content. ) 无论您是打算稍后显示图像,将它们导出到文件,还是将图像打印到启用 AirPrint 的打印机,屏幕外绘图都可以在不中断用户工作流程的情况下执行此操作。
  • 为应用添加 AirPrint 支持。

At a Glance

iOS 原生图形系统结合了三种主要技术:UIKit ,Core Graphics 和 Core Animation 。 UIKit 在这些视图中提供视图和一些高级绘图功能, Core Graphics 在 UIKit 视图中提供额外的(低级)绘图支持, Core Animation 提供了将变换和动画应用于 UIKit 视图的功能。 Core Animation 还负责视图合成(view compositing)。

Custom UI Views Allow Greater Drawing Flexibility

本文档介绍如何使用原生绘图技术绘制自定义 UI 视图。 这些技术包括 Core Graphics 和 UIKit 框架,支持 2D 绘图。

在考虑使用自定义 U I视图之前,应确保确实需要这样做。 原生绘图适用于处理更复杂的2D布局需求。 但是,由于自定义视图是处理器密集型(processor-intensive)的,因此应限制使用原生绘图技术执行的绘制量。

作为自定义绘图的替代方案,iOS应用程序可以通过其他几种方式在屏幕上绘制内容。

  • Using standard (built-in) views.
  • Using Core Animation layers.
  • Using OpenGL ES in a GLKit view or a custom view.
  • Using web content.

根据创建的应用程序类型,可能会使用很少或不使用自定义绘图代码。 虽然沉浸式(immersive apps)应用程序通常广泛使用自定义绘图代码,但实用程序和生产力应用程序(utility and productivity apps)通常可以使用标准视图和控件来显示其内容。

分情况来选择是否用自定义视图,尤其是那些需要动态实时改变的,则用自定义视图,例如:绘图应用,街机风格的游戏等,它们都是需要实时不断更新屏幕的。

因为自定义视图通常是处理器密集型的(GPU的帮助较少),如果可以使用标准视图执行所需操作,则应始终这样做。 此外,应该使自定义视图尽可能小,仅包含无法以任何其他方式绘制的内容,使用标准视图用于其他所有内容。 如果需要将标准 UI 元素与自定义绘图结合使用,请考虑使用 Core Animation 图层将自定义视图与标准视图叠加,以便尽可能少地绘制。(ps: 尽量减少自定义绘制,减轻 CPU 的工作)

A Few Key Concepts Underpin Drawing With the Native Technologies

使用 UIKit 和 Core Graphics 绘制内容时,除了视图绘制周期外,还应该熟悉一些以下概念:

  • 对于 drawRect: 方法, UIKit 创建用于渲染到显示的图形上下文(graphics context)。 此图形上下文包含绘图系统执行绘图命令所需的信息,包括填充和描边颜色,字体,剪切区域和线宽等属性。 还可以为位图图像和 PDF 内容创建和绘制自定义图形上下文。
  • UIKit有一个默认坐标系(default coordinate system),它绘图的原点位于视图的左上角; 正值向下延伸到该原点的右侧。 可以通过修改当前变换矩阵(the current transformation matrix)来更改默认坐标系相对于基础视图或窗口的大小,方向和位置,该矩阵将视图的坐标空间映射到设备屏幕。
  • 在 iOS 中,测量点中距离的逻辑坐标空间(logical coordinate space)不等于以像素为单位测量的设备坐标空间(device coordinate space)。 为了获得更高的精度,点以浮点值表示。

UIKit, Core Graphics, and Core Animation Give Your App Many Tools For Drawing

UIKit 和 Core Graphics 具有许多互补的图形功能,包括图形上下文,贝塞尔曲线路径,图像,位图,透明层,颜色,字体, PDF 内容以及绘图矩形和剪切区域。 此外, Core Graphics 具有与线属性,颜色空间,图案颜色,渐变,阴影和图像蒙版相关的功能。 Core Animation 框架则可以通过操纵和显示使用其他技术创建的内容来创建流畅的动画。

Apps Can Draw Into Offscreen Bitmaps or PDFs

应用程序在离屏绘制内容通常很有用:

  • 在缩小照片以进行上传,将内容渲染到图像文件以用于存储目的时,或者使用 Core Graphics 生成用于显示的复杂图像时,通常使用屏幕外位图上下文。(ps: 用于存储)
  • 在绘制用户生成的内容以进行打印时,通常会使用屏外 PDF 上下文。

创建离屏上下文后,可以像在自定义视图的 drawRect: 方法中绘制一样绘制它。

Apps Have a Range of Options for Printing Content

iOS 4.2 后支持的。

It’s Easy to Update Your App for High-Resolution Screens

某些 iOS 设备具有高分辨率屏幕,因此应用必须准备好在这些设备和具有较低分辨率屏幕的设备上运行。 iOS 处理不同分辨率所需的大部分工作,但应用必须完成其余的工作。 这些任务包括提供特别命名的高分辨率图像,并修改与图层和图像相关的代码,以考虑当前的比例因子(scale factor)。

其他

打印的完整例子,可以看以下源码

iOS Drawing Concepts

高质量的图形是应用程序用户界面的重要组成部分。 提供高质量的图形不仅使应用程序看起来很好,而且还使应用程序看起来像是系统其余部分的自然扩展。 iOS提供了两种在系统中创建高质量图形的主要途径:OpenGL 和使用 Quartz ,Core Animation 和 UIKit 的原生渲染。 本文档描述了原生渲染。 (要了解OpenGL绘图,请参阅OpenGL ES Programming Guide。)

Quartz 是主要的绘图界面,支持基于路径的绘制,消除锯齿渲染,渐变填充图案,图像,颜色,坐标空间转换以及 PDF 文档创建,显示和解析。 UIKit 为线条艺术,为 Quartz 图像和颜色处理提供 Objective-C 包装。 Core Animation 为许多 UIKit 视图属性中的更改动画提供了底层支持,还可用于实现自定义动画。

本章概述了 iOS 应用程序的绘图过程,以及每种支持的绘图技术的特定绘图技术。 还可以找到有关如何针对 iOS 平台优化绘图代码的提示和指导。

要点:并非所有UIKit类都是线程安全的。 在应用程序主线程以外的线程上执行与绘图相关的操作之前,请务必查看文档。

The UIKit Graphics System

在 iOS 中,无论是否涉及 OpenGL Quartz UIKit 或 Core Animation ,所有绘制到屏幕的内容都发生在 UIView 类的实例或其子类。 视图定义了发生绘图的屏幕部分。 如果使用系统提供的视图,则会自动处理此图形。 但是,如果定义自定义视图,则必须自己提供绘图代码。 如果使用 Quartz , Core Animation 和 UIKit 进行绘制,则使用以下各节中描述的绘图概念。

除了直接绘制到屏幕外, UIKit 还允许绘制到屏幕外的位图和PDF图形上下文。 在非屏幕上下文中绘制时,没有在视图中绘制,这意味着视图绘制周期等概念不适用(除非获取该图像并在图像视图中绘制它或类似图像)。 (ps: 离屏幕渲染不会走绘制的流程。视图绘制就是屏幕上绘制)

The View Drawing Cycle

UIView 类的子类的基本绘图模型涉及按需更新内容。 UIView 类使更新过程更容易,更有效;不管怎样,通过收集你的更新请求,可以在最合适的时间将它们交付给绘图代码

视图首次显示或需要重绘视图的一部分时,iOS 会要求视图通过调用它的 drawRect: 方法来绘制其内容。
有几个操作可以触发视图更新:

  • 移动或删除部分遮挡视图的其他视图
  • 通过将其 hidden 属性设置为 NO ,可以再次显示先前隐藏的视图
  • 滚动到屏幕外的视图,然后返回到屏幕上
  • 显式调用视图的 setNeedsDisplaysetNeedsDisplayInRect: 方法

系统视图会自动重绘。 对于自定义视图,必须覆盖 drawRect: 方法并在其中执行所有绘制。 在 drawRect: 方法中,使用原生绘图技术绘制所需的形状,文本,图像,渐变或任何其他可视内容。 当自定义视图第一次显示时, iOS 会将一个矩形传递给视图的 drawRect: 方法,该方法包含视图的整个可见区域。 在后续调用期间,该矩形仅包含实际需要重绘的视图部分。 为获得最佳性能,应仅重绘受影响的内容。 (ps: 按需绘制)

调用 drawRect:方法后,视图将自身标记为已更新,并等待新操作到达并触发另一个更新周期。 如果视图显示静态内容,那么您需要做的就是响应视图因滚动和其他视图的存在而导致的可见性变化。(ps: 还是按需绘制)

但是,如果要更改视图的内容,则必须告诉视图重绘其内容。 为此,调用 setNeedsDisplaysetNeedsDisplayInRect: 方法以触发更新。 例如,如果每秒多次更新内容,则可能需要设置计时器以更新视图。 还可以更新视图以响应用户交互或在视图中创建新内容。

要点:不要自己调用视图的 drawRect: 方法。 只有在屏幕重绘期间内置于 iOS 中的代码才能调用该方法。 在其他时候,不存在图形上下文,因此无法绘图。 (图形上下文将在下一节中介绍。)

Coordinate Systems and Drawing in iOS

当应用程序在iOS中绘制内容时,它必须在由坐标系定义的二维空间中定位绘制的内容。 这个概念乍一看似乎很简单,但事实并非如此。 iOS 中的应用有时必须在绘制时处理不同的坐标系。

在 iOS 中,所有绘图都在图形上下文中进行。 从概念上讲,图形上下文是描述绘图应在何处以及如何发生的对象,包括基本绘图属性,例如绘制时使用的颜色,剪切区域,线宽和样式信息,字体信息,合成选项等。

如下面这张图所示,每个图形上下文都有一个坐标系。 更确切地说,每个图形上下文都有三个坐标系:

  • 绘图(用户)坐标系(The drawing (user) coordinate system.)。 发出绘图命令时使用此坐标系。
  • 视图坐标系(基本空间)(The view coordinate system (base space).)。 该坐标系是相对于视图的固定坐标系。
  • (物理)设备坐标系 (The (physical) device coordinate system.)。 该坐标系表示物理屏幕上的像素。

The relationship between drawing coordinates, view coordinates, and hardware coordinates

iOS 的绘图框架创建图形上下文,用于绘制到特定目的地,例如:屏幕,位图,PDF 内容等,并且这些图形上下文为该目标建立初始绘图坐标系。 此初始绘图坐标系称为默认坐标系(default coordinate system),是视图底层坐标系上的 1:1 映射。

每个视图还具有当前变换矩阵(CTM),这是一种将当前绘图坐标系中的点映射到(固定)视图坐标系的数学矩阵。 应用程序可以修改此矩阵(如稍后所述)以更改将来绘制操作的行为。

iOS 的每个绘图框架都基于当前图形上下文建立默认坐标系。 在iOS中,有两种主要类型的坐标系:

  • An upper-left-origin coordinate system (ULO). UIKit and Core Animation frameworks
  • A lower-left-origin coordinate system (LLO). Core Graphics

coordinate systems

注意:OS X中的默认坐标系是基于 LLO 的。 尽管 Core Graphics 和 AppKit 框架的绘图功能和方法非常适合此默认坐标系,但 AppKit 提供了编程支持,可以将绘图坐标系翻转为左上角原点。

在调用视图的 drawRect: 方法之前, UIKit 通过为绘图操作提供图形上下文来建立绘制到屏幕的默认坐标系。 在视图的 drawRect: 方法中,应用程序可以设置图形状态参数(例如填充颜色)并绘制到当前图形上下文,而无需显式引用图形上下文。 此隐式图形上下文建立 ULO 默认坐标系。

Points Versus Pixels

具体的看 http://joakimliu.github.io/2019/02/24/wwdc-2011-129/ 里面有讲到。

Obtaining Graphics Contexts

大多数情况下,图形上下文已经为你配置好了。 每个视图对象都会自动创建一个图形上下文,以便代码可以在调用自定义 drawRect: 方法后立即开始绘制。 作为此配置的一部分,底层 UIView 类为当前绘图环境创建图形上下文(CGContextRef opaque类型)。

如果要绘制视图以外的其他位置(例如,捕获PDF或位图文件中的一系列绘图操作),或者需要调用需要上下文对象的 Core Graphics 函数,则必须执行其他步骤,获取图形上下文对象。 以下部分解释了如何。

有关图形上下文,修改图形状态信息以及使用图形上下文创建自定义内容的更多信息,请参阅Quartz 2D Programming Guide。 有关与图形上下文结合使用的函数列表,请参阅 CGContext Reference , CGBitmapContext Reference 和 CGPDFContext Reference 。

Drawing to the Screen

如果使用 Core Graphics 函数绘制视图,无论是在 drawRect: 方法还是其他地方,都需要一个图形上下文来绘制。 (许多这些函数的第一个参数必须是CGContextRef对象。)可以调用函数 UIGraphicsGetCurrentContext 来获取在 drawRect 中隐含的相同图形上下文的显式版本。 因为它是相同的图形上下文,所以绘图函数也应该引用ULO默认坐标系。

如果要使用 Core Graphics 函数在 UIKit 视图中绘制,则应使用 UIKit 的 ULO 坐标系进行绘图操作。 或者,可以将翻转变换(flip transform)应用于 CTM ,然后使用 Core Graphics 原生 LLO 坐标系在 UIKit 视图中绘制对象。 “#Flipping the Default Coordinate System#”详细讨论了翻转变换。

UIGraphicsGetCurrentContext 函数始终返回当前有效的图形上下文。 例如,如果创建PDF上下文然后调用UIGraphicsGetCurrentContext ,将收到该 PDF 上下文。 如果使用 Core Graphics 函数绘制视图,则必须使用 UIGraphicsGetCurrentContext 返回的图形上下文。

Drawing to Bitmap Contexts and PDF Contexts

UIKit 提供了在位图图形上下文中渲染图像的功能,以及通过绘制PDF图形上下文来生成PDF内容的功能。 这两种方法都要求您首先分别调用创建图形上下文的函数 - 位图上下文或PDF上下文。 返回的对象充当后续绘图和状态设置调用的当前(和隐式)图形上下文。 在上下文中完成绘制后,可以调用另一个函数来关闭该上下文。

UIKit 提供的位图上下文和PDF上下文都建立了 ULO 默认坐标系。 Core Graphics 具有相应的功能,用于在位图图形上下文中进行渲染以及在PDF图形上下文中进行绘制。 但是,应用程序直接通过 Core Graphic s创建的上下文建立了 LLO 默认坐标系。

注意:在 iOS 中,建议使用 UIKit 函数绘制到位图上下文和 PDF 上下文。 但是,如果确实使用了 Core Graphics 替代方案并打算显示渲染结果,则必须调整代码以补偿默认坐标系中的差异。 有关详细信息,请参阅 “#Flipping the Default Coordinate System#”。

Color and Color Spaces

iOS 支持 Quartz 提供的全系列颜色空间; 但是,大多数应用程序应该只需要 RGB 颜色空间。 由于iOS设计为在嵌入式硬件上运行并在屏幕上显示图形,因此 RGB 颜色空间是最合适的颜色空间

UIColor 对象提供了使用 RGB , HSB和灰度值指定颜色值的便捷方法。 以这种方式创建颜色时,您永远不需要指定颜色空间。 它由 UIColor 对象自动确定。

您还可以使用 Core Graphics 框架中的 CGContextSetRGBStrokeColorCGContextSetRGBFillColor 函数来创建和设置颜色。 虽然 Core Graphics 框架包括支持使用其他颜色空间创建颜色,以及创建自定义颜色空间,但不建议在绘图代码中使用这些颜色。 您的绘图代码应始终使用 RGB 颜色

Drawing with Quartz and UIKit

Quartz 是 iOS 中原生绘图技术的通用名称。 Core Graphics 框架是 Quartz 的核心,也是用于绘制内容的主要界面。 该框架提供了用于操作以下内容的数据类型和函数:

  • Graphics contexts
  • Paths
  • Images and bitmaps
  • Transparency layers
  • Colors, pattern colors, and color spaces
  • Gradients and shadings
  • Fonts
  • PDF content

UIKit 通过为图形相关操作提供一组集中的类来构建 Quartz 的基本功能。 UIKit 图形类并不是一套全面的绘图工具– Core Graphics 已经提供了这些工具。 相反,它们为其他 UIKit 类提供绘图支持。 UIKit 支持包括以下类和功能:

  • UIImage, which implements an immutable class for displaying images
  • UIColor, which provides basic support for device colors
  • UIFont, which provides font information for classes that need it
  • UIScreen, which provides basic information about the screen
  • UIBezierPath, which enables your app to draw lines, arcs, ovals, and other shapes.
  • Functions for generating a JPEG or PNG representation of a UIImage object
  • Functions for drawing to a bitmap graphics context
  • Functions for generating PDF data by drawing to a PDF graphics context
  • Functions for drawing rectangles and clipping the drawing area
  • Functions for changing and getting the current graphics context

Configuring the Graphics Context

在调用 drawRect: 方法之前,视图对象会创建图形上下文并将其设置为当前上下文。 此上下文仅存在于 drawRect: 方法调用的生命周期中。 可以通过调用 UIGraphicsGetCurrentContext 函数来获取指向此图形上下文的指针。 此函数返回对 CGContextRef 类型的引用,将其传递给 Core Graphics 函数以修改当前图形状态。 下表列出了用于设置图形状态不同方面的主要功能。 有关函数的完整列表,请参阅CGContext Reference。 该表还列出了 UIKit 存在的替代方案。

Graphics state Core Graphics functions UIKit alternative
Current transformation matrix (CTM) CGContextRotateCTM CGContextScaleCTM CGContextTranslateCTM CGContextConcatCTM None
Clipping area CGContextClipToRect UIRectClip function
Line: Width, join, cap, dash, miter limit CGContextSetLineWidth CGContextSetLineJoin CGContextSetLineCap CGContextSetLineDash CGContextSetMiterLimit None
Accuracy of curve estimation CGContextSetFlatness None
Anti-aliasing setting CGContextSetAllowsAntialiasing None
Color: Fill and stroke settings CGContextSetRGBFillColor CGContextSetRGBStrokeColor UIColor class
Alpha global value (transparency) CGContextSetRenderingIntent None
Rendering intent CGContextSetRenderingIntent None
Color space: Fill and stroke settings CGContextSetFillColorSpace CGContextSetStrokeColorSpace UIColor class
Text: Font, font size, character spacing, text drawing mode CGContextSetFont CGContextSetFontSize CGContextSetCharacterSpacing UIFont class
Blend mode CGContextSetBlendMode The UIImage class and various drawing functions let you specify which blend mode to use.

图形上下文包含已保存的图形状态的栈。 当 Quartz 创建图形上下文时,栈为空。 使用 CGContextSaveGState 函数将当前图形状态的副本推送到栈。 此后,对图形状态所做的修改会影响后续的绘图操作,但不会影响存储在栈中的副本。 完成修改后,可以使用 CGContextRestoreGState 函数将保存的状态弹出堆栈顶部,从而返回到先前的图形状态。 以这种方式推送(push)和弹出(pop)图形状态是返回先前状态的快速方法,并且无需单独撤消每个状态更改。 它也是将状态的某些方面(例如剪切路径)恢复到其原始设置的唯一方法。

Creating and Drawing Paths

路径是从一系列线和贝塞尔曲线创建的基于矢量的形状。 UIKit 包含 UIRectFrameUIRectFill 函数(以及其他函数),用于在视图中绘制简单路径,例如矩形。 Core Graphics 还包括用于创建简单路径(如矩形和椭圆)的便捷功能。

对于更复杂的路径,必须使用 UIKit 的 UIBezierPath 类自己创建路径,或者使用在 Core Graphics 框架中对 CGPathRef opaque 类型进行操作的函数。 虽然可以使用任一API构建没有图形上下文的路径,但路径中的点仍然必须引用当前坐标系(具有ULO或LLO方向),并且仍需要图形上下文来实际呈现路径。

绘制路径时,必须设置当前上下文。 此上下文可以是自定义视图的上下文(在 drawRect:)中,位图上下文或 PDF 上下文。 坐标系确定路径的呈现方式。 UIBezierPath 假定 ULO 坐标系。 因此,如果您的视图被翻转(使用 LLO 坐标),则生成的形状可能会呈现与预期不同的形状。 为获得最佳结果,应始终指定相对于用于渲染的图形上下文的当前坐标系原点的点

注意:即使遵循此“规则”,弧(Arc)是需要额外工作的路径的一个方面。 如果使用 Core Graphic 函数创建路径,该函数定位 ULO 坐标系中的点,然后在 UIKit 视图中渲染路径,则弧“指向”的方向不同。 有关此主题的更多信息,请参阅”#Side Effects of Drawing with Different Coordinate Systems#”。

要在iOS中创建路径,建议使用 UIBezierPath 而不是 CGPath 函数,除非需要一些仅 Core Graphics 提供的功能,例如向路径添加椭圆。 有关在 UIKit 中创建和渲染路径的更多信息,请参阅”#Drawing Shapes Using Bézier Paths#”。

Creating Patterns, Gradients, and Shadings

Core Graphics 框架包含用于创建模板(pattern),渐变和阴影的附加功能。 您可以使用这些类型创建非单色颜色,并使用它们填充您创建的路径。 模板是根据重复的图像或内容创建的。 渐变和阴影提供了不同的方法来创建从颜色到颜色的平滑过渡。

Quartz 2D Programming Guide 中包含了创建和使用模式,渐变和阴影的详细信息。

Customizing the Coordinate Space

默认情况下, UIKit 创建一个直接的 CTM ,将点映射到像素上。 虽然可以在不修改该矩阵的情况下完成所有绘图,但有时这样做很方便。

首次调用视图的 drawRect: 方法时, CTM 已经配置好,使该坐标系(绘制)的原点与视图的原点匹配,正 X 轴向右延伸,正 Y 轴向下延伸。 但是,可以通过向其添加缩放,旋转和平移因子来更改 CTM ,从而更改默认坐标系相对于基础视图或窗口的大小,方向和位置。

Using Coordinate Transforms to Improve Drawing Performance

修改 CTM 是在视图中绘制内容的标准技术,因为它允许您重用路径,这可能会减少绘制时所需的计算量。 例如,如果要从点(20,20)开始绘制一个正方形,则可以创建一个移动到(20,20)的路径,然后绘制所需的一组线以完成该正方形。 但是,如果后面决定将该方块移动到该点(10,10),则必须使用新起点重新创建路径。 因为创建路径是相对昂贵的操作,所以最好创建一个原点为(0,0)的正方形并修改CTM,以便在所需的原点绘制正方形。

在 Core Graphics 框架中,有两种方法可以修改CTM。 可以使用CGContext Reference中定义的 CTM 操作函数直接修改 CTM 。 还可以创建CGAffineTransform结构,应用所需的任何转换,然后将该转换连接到 CTM 。 使用仿射变换可以对变换进行组装,然后将它们一次性应用到 CTM 。 还可以评估和转化仿射变换,使用它们来修改代代码中的点,大小和矩形值。 有关使用仿射变换的更多信息,请参阅Quartz 2D Programming GuideCGAffineTransform Reference

Flipping the Default Coordinate System

在 UIKit 绘图中翻转会修改背景(backing)CALayer,以将具有 LLO 坐标系的绘图环境与 UIKit 的默认坐标系对齐。 如果只使用 UIKit 方法和绘图功能,则不需要翻转 CTM 。 但是,如果将 Core Graphics 或 Image I/O 函数调用与 UIKit 调用混合使用,则可能需要翻转 CTM 。

尤其,如果通过直接调用 Core Graphics 函数绘制图像或 PDF 文档,则该对象将在视图的上下文中呈现为倒置。 必须翻转 CTM 才能正确显示图像和页面。

要将绘制到 Core Graphics 上下文的对象翻转,以便在 UIKit 视图中显示时正确显示,必须分两步修改CTM。 将原点转换为绘图区域的左上角,然后应用缩放平移,将 y 坐标修改为 -1。 执行此操作的代码类似于以下内容:

1
2
3
4
5
CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);

如果创建使用 Core Graphics 图像对象初始化的UIImage对象,UIKit 会为您执行翻转变换。 每个 UIImage 对象都由 CGImageRef opaque 类型支持(backed)。 可以通过 CGImage 属性访问 Core Graphics 对象,并对图像进行一些操作。 (Core Graphics 具有 UIKit 中不可用的图像相关功能。)完成后,可以从修改后的 CGImageRef 对象重新创建 UIImage 对象。

注意:可以使用 Core Graphics 函数 CGContextDrawImage 将图像绘制到任何渲染目标。 此函数有两个参数,第一个用于图形上下文,第二个用于矩形,用于定义图像的大小及其在绘图表面(如视图)中的位置。 使用 CGContextDrawImage 绘制图像时,如果不将当前坐标系调整为LLO方向,则图像在UIKit视图中显示为倒置。 此外,传递给此函数的矩形的原点是相对于调用函数时当前坐标系的原点

Side Effects of Drawing with Different Coordinate Systems

当使用一个绘图技术的默认坐标系绘制对象然后在另一个绘图技术的图形上下文中渲染时,会显示一些渲染奇怪现象。 可能需要调整代码以解决这些副作用。

Arcs and Rotations

如果使用 CGContextAddArc 和 CGPathAddArc 等函数绘制路径并假设 LLO 坐标系,则需要翻转 CTM 以在 UIKit 视图中正确渲染弧。 但是,如果使用相同的函数创建包含位于 ULO 坐标系中的点的弧,然后在 UIKit 视图中渲染路径,将注意到弧是其原始的更改版本。 现在,弧的终止端点指向与使用 UIBezierPath 类创建的弧所做的端点相反的方向。 例如,向下箭头现在指向上方(如图1-5所示),弧“弯曲”的方向也不同。 所以必须更改 Core Graphics 绘制弧的方向以考虑基于 ULO 的坐标系; 此方向由这些函数的 startAngle 和 endAngle 参数控制。

Figure 1-5  Arc rendering in Core Graphics versus UIKit

如果旋转对象,则可以观察到相同类型的镜像效果(例如,通过调用 CGContextRotateCTM )。 如果使用引用 ULO 坐标系的 Core Graphics 调用旋转对象,则在 UIKit 中渲染时对象的方向将反转。 必须在代码中考虑不同的轮换方向; 使用 CGContextRotateCTM ,可以通过反转角度参数的符号来执行此操作(例如,负值变为正值)。

Shadows

阴影从其对象落下的方向由偏移值指定,该偏移的含义是绘图框架的约定。 在 UIKit 中,正 x 和 y 偏移使阴影向下并且在对象的右侧。 在 Core Graphics 中,正 x 和 y 偏移会使阴影上升到对象的右侧。 翻转 CTM 以使对象与 UIKit 的默认坐标系对齐不会影响对象的阴影,因此阴影无法正确跟踪其对象。 要使其正确跟踪,必须适当修改当前坐标系的偏移值。

Applying Core Animation Effects

Core Animation 是一个 Objective-C 框架,为快速,轻松地创建流畅的实时动画提供基础设施。 核心动画本身并不是绘图技术,因为它不提供用于创建形状,图像或其他类型内容的原始例程。 相反,它是一种操纵和显示 您使用其他技术创建的 内容 的技术。

大多数应用程序都可以从 iOS 中以某种形式使用 Core Animation 中受益。 动画向用户提供有关正在发生的事情的反馈。 例如,当用户浏览“设置”应用时,屏幕会根据用户是否在首选项层次结构中向下导航或返回到根节点而滑入和滑出视图。 这种反馈很重要,并为用户提供上下文信息。 它还增强了应用程序的视觉风格。

在大多数情况下,您可以轻松地获得 Core Animation 的好处。 例如, UIView 类的几个属性(包括视图的框架,中心,颜色和不透明度等)可以配置为在其值发生变化时触发动画。 您必须做一些工作才能让 UIKit 知道您希望执行这些动画,但动画本身会自动创建并运行。 有关如何触发内置视图动画的信息,请参阅UIView Class Reference中的动画视图。

当超越基本动画时,您必须更直接地与 Core Animation 类和方法进行交互。 以下部分提供有关 Core Animation 的信息,并向您展示如何使用其类和方法在 iOS 中创建典型动画。 有关 Core Animation 及其使用方法的其他信息,请参阅Core Animation Programming Guide

About Layers

核心动画中的关键技术是图层对象。 图层是轻量级对象,在本质上与视图类似,但实际上是模型对象,它们封装了要显示的内容的几何,时间和视觉属性。 内容本身以三种方式之一提供:

  • You can assign a CGImageRef to the contents property of the layer object.
  • You can assign a delegate to the layer and let the delegate handle the drawing.
  • You can subclass CALayer and override one of the display methods.

操作图层对象的属性时,实际操作的是模型级数据,用于确定应如何显示关联内容。 该内容的实际渲染是与代码分开处理的,并经过大量优化以确保其快速。 您所要做的就是设置图层内容,配置动画属性,然后让 Core Animation 接管。有关图层及其使用方式的更多信息,请参阅Core Animation Programming Guide

About Animations

在动画图层时, Core Animation 使用单独的动画对象来控制动画的时间和行为。 CAAnimation 类及其子类提供了可在代码中使用的不同类型的动画行为。 可以创建将属性从一个值迁移到另一个值的简单动画,也可以创建复杂的关键帧动画,即通过提供的一组值和计时功能来跟踪动画。

Core Animation 还允许将多个动画组合到一个单元中,称为事务。 CATransaction 对象将动画组作为一个单元进行管理。 还可以使用此类的方法来设置动画的持续时间。

有关如何创建自定义动画的示例,请参阅Animation Types and Timing Programming Guide

Accounting for Scale Factors in Core Animation Layers

直接使用 Core Animation 图层提供内容的应用可能需要调整其绘图代码以考虑比例因子。 通常,当在视图的 drawRect: 方法中绘制时,或者在图层委托drawLayer:inContext: 方法中绘制时,系统会自动调整图形上下文以考虑比例因子。 但是,当视图执行以下操作之一时,可能仍然需要了解或更改该比例因子

  • 创建具有不同比例因子的其他 Core Animation 图层,并将它们合成为自己的内容 “Creates additional Core Animation layers with different scale factors and composites them into its own content”
  • 直接设置 Core Animation 图层的 contents 属性 “Sets the contents property of a Core Animation layer directly”

Core Animation 的合成引擎查看每个图层的 contentsScale 属性,以确定在合成期间是否需要缩放该图层的内容。 如果应用创建没有关联视图的图层,则每个新图层对象的比例因子最初设置为 1.0 。 如果不更改该比例因子,并且随后在高分辨率屏幕上绘制该图层,则会自动缩放图层的内容以补偿(compensate)比例因子的差异。 如果不希望缩放内容,可以通过为 contentsScale 属性设置新值来将图层的比例因子更改为 2.0 ,但如果这样做但是不提供高分辨率内容,则现有内容可能会比期待的要小。 要解决该问题,则需要为图层提供更高分辨率的内容。

要点:图层的 contentsGravity 属性 在确定标准分辨率图层内容是否在高分辨率屏幕上缩放时 起作用。 默认情况下,此属性设置为值 kCAGravityResize ,这会导致图层内容缩放以适合图层的边界。 将该属性更改为非尺寸选项可消除否则会发生的自动缩放。 在这种情况下,您可能需要相应地调整内容或比例因子。

当直接设置图层的 contents 属性时,调整图层的内容以适应不同的比例因子是最合适的。 Quartz图像没有比例因子的概念,因此直接与像素一起工作。 因此,在创建计划用于图层内容的 CGImageRef 对象之前,请检查比例因子并相应地调整图像的大小。 具体来说,从应用程序包中加载适当大小的图像,或使用 UIGraphicsBeginImageContextWithOptions 函数创建一个图像,其比例因子与图层的比例因子相匹配。 如果不创建高分辨率位图,则可以如前所述缩放现有位图。

有关如何指定和加载高分辨率图像的信息,请参阅”#Loading Images into Your App#”。 有关如何创建高分辨率图像的信息,请参阅”#Drawing to Bitmap Contexts and PDF Contexts#”。

Drawing Shapes Using Bézier Paths

在 iOS 3.2 及更高版本中,可以使用 UIBezierPath 类创建基于矢量的路径。 UIBezierPath 类是 Core Graphics 框架中与路径相关的功能的 Objective-C 包装器。 可以使用此类定义简单形状,例如椭圆和矩形,以及包含多个直线和曲线线段的复杂形状。(ps: UIKit 都对 Core Graphics 的相关功能进行了封装。)

可以使用路径对象在应用程序的用户界面中绘制形状(draw shapes)。 可以绘制路径的轮廓,填充它所包含的空间,或两者。 还可以使用路径为当前图形上下文定义剪切区域,然后可以使用该剪辑区域修改该上下文中的后续绘制操作。(ps: 这里有一个 dmeo: Quartz2D for iOS)

Bézier Path Basics

UIBezierPath 对象是 CGPathRef 数据类型的包装器。 路径是使用直线和曲线段构建的基于矢量的形状。 您可以使用线段创建矩形和多边形,也可以使用曲线段创建圆弧,圆和复杂的曲线形状。 每个段由一个或多个点(在当前坐标系中)和一个绘图命令组成,该命令定义这些点是如何解释(ps: interpreted 这里翻译成处理会更好)。

每组连接的线和曲线段形成所谓的子路径。 子路径中一行或曲线段的末尾定义下一行的开头。 单个 UIBezierPat h对象可以包含一个或多个定义整个路径的子路径,由moveToPoint:命令分隔,这些命令可以有效地提升绘图笔并将其移动到新位置。

构建和使用路径对象的过程是分开的。 构建路径是第一个过程,涉及以下步骤:

  1. 创建路径对象。
  2. 设置UIBezierPath对象的任何相关绘图属性,例如描边路径的lineWidth或lineJoinStyle属性或已填充路径的usesEvenOddFillRule属性。 这些绘图属性适用于整个路径。
  3. 使用moveToPoint:方法设置初始段的起始点。
  4. 添加线和曲线段以定义子路径。
  5. (可选)通过调用closePath关闭子路径,closePath从最后一个段的末尾到第一个段的开头绘制一条直线段
  6. (可选)重复步骤3,4和5以定义其他子路径。

构建路径时,应该相对于原点(0,0)排列路径的点。 这样做可以更轻松地在以后移动路径。 在绘制过程中,路径的点将按原样应用于当前图形上下文的坐标系。 如果您的路径相对于原点定向,当重新定位时,你所需要做的就,将具有平移因子的仿射变换应用于当前图形上下文。 修改图形上下文(与路径对象本身相对)的优点是,您可以通过保存和恢复图形状态轻松撤消转换

要绘制路径对象,请使用描边和填充方法(stroke and fill)。 这些方法在当前图形上下文中渲染路径的线段和曲线段。 渲染过程涉及使用路径对象的属性栅格化线和曲线段。 栅格化过程不会修改路径对象本身。 因此,您可以在当前上下文或另一个上下文中多次渲染相同的路径对象。
(ps: 栅格化到底是个什么鬼?)

Adding Lines and Polygons to Your Path

线条和多边形是 使用 moveToPoint:addLineToPoint: 方法逐点构建的 简单形状。 moveToPoint: 方法设置 要创建的形状的 起点。 从那个点起,您可以使用 addLineToPoint: 方法创建形状的线条。 还可以连续创建线条,每条线条都在前一个点和您指定的新点之间形成。

清单2-1显示了使用单独的线段创建五边形形状所需的代码。 (图2-1显示了 使用适当的笔触和填充颜色设置绘制此形状的 结果,如”#Rendering the Contents of a Bézier Path Object#”所述。)此代码设置形状的初始点,然后添加四个连接的线段。 通过调用closePath方法添加第五个段,该方法将最后一个点(0,40)与第一个点(100,0)连接起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Listing 2-1  Creating a pentagon shape

UIBezierPath *aPath = [UIBezierPath bezierPath];

// Set the starting point of the shape.
[aPath moveToPoint:CGPointMake(100.0, 0.0)];

// Draw the lines.
[aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[aPath addLineToPoint:CGPointMake(160, 140)];
[aPath addLineToPoint:CGPointMake(40.0, 140)];
[aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[aPath closePath];

Figure 2-1  Shape drawn with methods of the UIBezierPath class

使用closePath方法不仅会结束描述形状的子路径,还会在第一个和最后一个点之间绘制一条线段。 这是完成多边形而不必绘制最终线的便捷方法。

Adding Arcs to Your Path

UIBezierPath 类支持使用弧段初始化新路径对象。bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise: 方法的参数 定义了包含所需弧的圆以及弧本身的起点和终点。 图2-2显示了创建弧的组件,包括定义弧的圆和用于指定弧的角度测量。 在这种情况下,弧沿顺时针方向创建。 (以逆时针方向绘制圆弧将改为绘制圆的虚线部分。)创建此弧的代码如清单2-2所示。

Figure 2-2  An arc in the default coordinate system

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Listing 2-2  Creating a new arc path

// pi is approximately equal to 3.14159265359.
#define DEGREES_TO_RADIANS(degrees) ((pi * degrees)/ 180)

- (UIBezierPath *)createArcPath
{
UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150)
radius:75
startAngle:0
endAngle:DEGREES_TO_RADIANS(135)
clockwise:YES];
return aPath;
}

如果要将弧段合并到路径的中间,则必须直接修改路径对象的CGPathRef数据类型。 有关使用Core Graphics函数修改路径的更多信息,请参阅”#Modifying the Path Using Core Graphics Functions#”。

Adding Curves to Your Path

UIBezierPath 类支持将三次(Cubic)和二次(Quadratic) 贝塞尔曲线添加到路径。 曲线段从当前点开始,到您指定的点结束。 使用起点和终点之间的切线(tangent)以及一个或多个控制点来定义曲线的形状。 图2-3显示了两种曲线类型的近似值以及控制点与曲线形状之间的关系。 每个段的精确曲率涉及所有点之间的复杂数学关系,并且在线文档和维基百科都有详细记录。

Figure 2-3  Curve segments in a path

要将曲线添加到路径,请使用以下方法:

  • Cubic curve: addCurveToPoint:controlPoint1:controlPoint2:
  • Quadratic curve: addQuadCurveToPoint:controlPoint:

由于曲线依赖于路径的当前点,因此必须在调用上述任一方法之前设置当前点。 完成曲线后,当前点将更新为您指定的新结束点。

Creating Oval and Rectangular Paths

椭圆和矩形是使用曲线和线段组合构建的常见路径类型。 UIBezierPath类包含bezierPathWithRect:和bezierPathWithOvalInRect:方便方法,用于创建椭圆或矩形形状的路径。这两种方法都创建了一个新的路径对象,并使用指定的形状对其进行初始化。您可以立即使用返回的路径对象,也可以根据需要添加更多形状。

如果要将矩形添加到现有路径对象,则必须使用moveToPoint:,addLineToPoint:和closePath方法,就像对任何其他多边形一样。对矩形的最后一侧使用closePath方法是 添加路径的最后一行并且还标记矩形子路径的末尾的 便捷方式。

如果要在现有路径中添加椭圆,最简单的方法是使用Core Graphics。 虽然您可以使用addQuadCurveToPoint:controlPoint:来近似椭圆曲面,但CGPathAddEllipseInRect函数使用起来更简单,更准确。 有关更多信息,请参阅”#Modifying the Path Using Core Graphics Functions#”。

Modifying the Path Using Core Graphics Functions

UIBezierPath 类实际上只是 CGPathRef 数据类型的包装器以及与该路径关联的绘图属性。 虽然通常使用U IBezierPath 类的方法添加线段和曲线段,但该类还公开了一个 CGPath 属性,可以使用该属性直接修改路径数据类型。 当您希望使用 Core Graphics 框架的功能构建路径时,可以使用此属性。

有两种方法可以修改与 UIBezierPath 对象关联的路径。 可以使用 Core Graphics 函数完全修改路径,也可以混合使用 Core Graphics 函数和 UIBezierPath 方法。 在某些方面,使用 Core Graphics 调用完全修改路径更容易。 可以创建可变 CGPathRef 数据类型并调用修改其路径信息所需的任何函数。 完成后,将路径对象分配给相应的 UIBezierPath 对象,如清单2-3所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Listing 2-3  Assigning a new CGPathRef to a UIBezierPath object

// Create the path data.
CGMutablePathRef cgPath = CGPathCreateMutable();
CGPathAddEllipseInRect(cgPath, NULL, CGRectMake(0, 0, 300, 300));
CGPathAddEllipseInRect(cgPath, NULL, CGRectMake(50, 50, 200, 200));

// Now create the UIBezierPath object.
UIBezierPath *aPath = [UIBezierPath bezierPath];
aPath.CGPath = cgPath;
aPath.usesEvenOddFillRule = YES;

// After assigning it to the UIBezierPath object, you can release
// your CGPathRef data type safely.
CGPathRelease(cgPath);

如果选择使用 Core Graphics 函数和 UIBezierPath 方法的混合,则必须在两者之间来回小心地移动路径信息。 因为 UIBezierPath 对象拥有其基础 CGPathRef 数据类型,所以不能简单地检索该类型并直接修改它。 相反,必须制作可变副本,修改副本,然后将副本分配回CGPath属性,如清单2-4所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Listing 2-4  Mixing Core Graphics and UIBezierPath calls

UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 300, 300)];

// Get the CGPathRef and create a mutable version.
CGPathRef cgPath = aPath.CGPath;
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(cgPath);

// Modify the path and assign it back to the UIBezierPath object.
CGPathAddEllipseInRect(mutablePath, NULL, CGRectMake(50, 50, 200, 200));
aPath.CGPath = mutablePath;

// Release both the mutable copy of the path.
CGPathRelease(mutablePath);

Rendering the Contents of a Bézier Path Object

创建 UIBezierPath 对象后,可以使用其描边和填充方法在当前图形上下文中渲染它。但是,在调用这些方法之前,通常还需要执行一些其他任务来确保正确绘制路径:

  • 使用 UIColor 类的方法设置所需的描边和填充颜色。
  • 将形状放在目标视图中所需的位置。
    如果创建了相对于点(0,0)的路径,则可以将适当的仿射变换应用于当前绘图上下文。例如,要从点(10,10)开始绘制形状,将调用 CGContextTranslateCTM 函数并为水平和垂直平移值指定 10 。首选调整图形上下文(而不是路径对象中的点),因为可以通过保存和恢复以前的图形状态来更轻松地撤消更改。
  • 更新路径对象的绘图属性。在渲染路径时, UIBezierPath 实例的绘图属性会覆盖与图形上下文关联的值。

清单2-5显示了 drawRect: 方法的示例实现,该方法在自定义视图中绘制椭圆。椭圆的边界矩形的左上角位于视图坐标系中的点(50,50)处。因为填充操作直接绘制到路径边界,所以此方法在描边之前填充路径。这可以防止填充颜色遮挡一半的描边线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Listing 2-5  Drawing a path in a view

- (void)drawRect:(CGRect)rect
{
// Create an oval shape to draw.
UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0, 0, 200, 100)];

// Set the render colors.
[[UIColor blackColor] setStroke];
[[UIColor redColor] setFill];

CGContextRef aRef = UIGraphicsGetCurrentContext();

// If you have content to draw after the shape,
// save the current state before changing the transform.
//CGContextSaveGState(aRef);

// Adjust the view's origin temporarily. The oval is
// now drawn relative to the new origin point.
CGContextTranslateCTM(aRef, 50, 50);

// Adjust the drawing options as needed.
aPath.lineWidth = 5;

// Fill the path before stroking it so that the fill
// color does not obscure the stroked line.
[aPath fill];
[aPath stroke];

// Restore the graphics state before drawing any other content.
//CGContextRestoreGState(aRef);
}

Doing Hit-Detection on a Path

要确定是否在路径的填充部分上发生了触摸事件,可以使用 UIBezierPath 的 containsPoint: 方法。此方法针对路径对象中所有已关闭的子路径测试指定点,如果它位于任何这些子路径上或内部,则返回YES。

要点: containsPoint: 方法和Core Graphics命中测试功能仅在封闭路径上运行。对于打开的子路径上的命中,这些方法始终返回 NO 。如果要在打开的子路径上执行命中检测,则必须先创建路径对象的副本,然后在测试点之前关闭打开的子路径。

如果要对路径的描边部分(而不是填充区域)进行命中测试(on the stroked portion of the path (instead of the fill area)),则必须使 用Core Graphics 。 CGContextPathContainsPoint 函数允许测试当前分配给图形上下文的路径的填充或描边部分上的点。清单2-6显示了一个方法,用于测试指定的点是否与指定的路径相交。 inFill 参数允许调用者指定是否应针对路径的填充或描边部分测试该点。调用者传入的路径必须包含一个或多个已关闭的子路径,以使命中检测成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Listing 2-6  Testing points against a path object

- (BOOL)containsPoint:(CGPoint)point onPath:(UIBezierPath *)path inFillArea:(BOOL)inFill
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGPathRef cgPath = path.CGPath;
BOOL isHit = NO;

// Determine the drawing mode to use. Default to
// detecting hits on the stroked portion of the path.
CGPathDrawingMode mode = kCGPathStroke;
if (inFill)
{
// Look for hits in the fill area of the path instead.
if (path.usesEvenOddFillRule)
mode = kCGPathEOFill;
else
mode = kCGPathFill;
}

// Save the graphics state so that the path can be
// removed later.
CGContextSaveGState(context);
CGContextAddPath(context, cgPath);

// Do the hit detection.
isHit = CGContextPathContainsPoint(context, point, mode);

CGContextRestoreGState(context);

return isHit;
}

Drawing and Creating Images

大多数情况下,使用标准视图显示图像非常简单。 但是,有两种情况您可能需要做额外的工作:

  • 如果要将图像显示为自定义视图的一部分,则必须在视图的 drawRect: 方法中自行绘制图像。 “#Drawing Images#” 解释了为什么。
  • 如果要在屏幕外渲染图像(稍后绘制或保存到文件中),则必须创建位图图像上下文。 要了解更多信息,请阅读”#Creating New Images Using Bitmap Graphics Contexts#”。

Drawing Images

为了获得最佳性能,如果使用 UIImageView 类可以满足图像绘制需求,则应使用此图像对象初始化 UIImageView 对象。 但是,如果需要显式绘制图像,则可以存储图像并稍后在视图的d rawRect: 方法中使用它。下面的代码是从 bundle 加载图片。

1
2
3
4
5
6
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"myImage" ofType:@"png"];
UIImage *myImageObj = [[UIImage alloc] initWithContentsOfFile:imagePath];

// Store the image into a property of type UIImage *
// for use later in the class's drawRect: method.
self.anImage = myImageObj;

要在视图的 drawRect: 方法中显式绘制生成的图像,可以使用 UIImage 中提供的任何绘图方法。 这些方法允许指定视图中要绘制图像的位置,因此不需要在绘制之前创建和应用单独的变换。

以下代码段在视图中的点(10,10)处绘制上面加载的图像。

1
2
3
4
5
6
7
- (void)drawRect:(CGRect)rect
{
...

// Draw the image.
[self.anImage drawAtPoint:CGPointMake(10, 10)];
}

要点:如果使用 CGContextDrawImage 函数直接绘制位图图像,则默认情况下图像数据沿 y 轴反转。 这是因为Quartz 图像假定坐标系具有左下角的原点,而正坐标轴从该点向上和向右延伸。 虽然可以在绘制之前应用变换,但绘制 Quartz 图像的简单(和推荐)方法是将它们包装在 UIImage 对象中,该对象可自动补偿坐标空间中的这种差异。 有关使用 Core Graphics 创建和绘制图像的更多信息,请参阅Quartz 2D Programming Guide

Creating New Images Using Bitmap Graphics Contexts

大部分时间,在绘图时,你的目标是在屏幕上显示某些内容。但是,将某些内容绘制到屏幕外缓冲区有时很有用。例如,可能希望创建现有图像的缩略图,绘制到缓冲区中以便将其保存到文件中,等等。为了支持这些需求,可以创建位图图像上下文,使用 UIKit 框架或 Core Graphics 函数绘制它,然后从上下文中获取图像对象。

在 UIKit 中,步骤如下:

  1. 调用 UIGraphicsBeginImageContextWithOptions 来创建位图上下文并将其推送到图形堆栈。
    对于第一个参数(大小),传递 CGSize 值以指定位图上下文的维度(以点为单位)。// Core Graphics 是以像素为单位的
    对于第二个参数(不透明),如果图像包含透明度(Alpha通道),则传递 NO。否则,传递 YES 以最大化性能。
    对于最终参数(比例),对于为设备主屏幕适当缩放的位图传递 0.0 ,或者传递您选择的比例因子。
    例如,以下代码段创建一个 200 x 200 像素的位图。 (像素数通过将图像的大小乘以比例因子来确定。UIGraphicsBeginImageContextWithOptions(CGSizeMake(100.0, 100.0),NO,2.0);

    注意:通常应该避免调用类似名称的 UIGraphicsBeginImageContext 函数(除了作为向后兼容性的后备),因为它始终创建比例因子为 1.0 的图像。如果底层设备具有高分辨率屏幕,则使用 UIGraphicsBeginImageContext 创建的图像在渲染时可能不会显示为平滑。

  2. 使用 UIKit 或 Core Graphics 例程将图像的内容绘制到新创建的图形上下文中。

  3. 调用 UIGraphicsGetImageFromCurrentImageContext 函数,根据绘制的内容生成并返回 UIImage 对象。如果需要,还可以继续绘制并再次调用此方法以生成其他图像。

  4. 调用 UIGraphicsEndImageContext 从图形堆栈中弹出上下文。

清单3-1中的方法得到一个 image 通过互联网下载并将其绘制到基于图像的上下文中,缩小到应用程序图标的大小。然后,它获取从位图数据创建的 UIImage 对象,并将其分配给实例变量。请注意,位图的大小(UIGraphicsBeginImageContextWithOptions的第一个参数)和绘制内容的大小(imageRect的大小)应该匹配。如果内容大于位图,则内容的一部分将被剪切而不会出现在结果图像中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Listing 3-1  Drawing a scaled-down image to a bitmap context and obtaining the resulting image

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
if (image != nil && image.size.width != kAppIconHeight && image.size.height != kAppIconHeight) {
CGRect imageRect = CGRectMake(0.0, 0.0, kAppIconHeight, kAppIconHeight);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, [UIScreen mainScreen].scale);
[image drawInRect:imageRect];
self.appRecord.appIcon = UIGraphicsGetImageFromCurrentImageContext(); // UIImage returned.
UIGraphicsEndImageContext();
} else {
self.appRecord.appIcon = image;
}
self.activeDownload = nil;
[image release];
self.imageConnection = nil;
[delegate appImageDidLoad:self.indexPathInTableView];
}

还可以调用 Core Graphics 函数来绘制生成的位图图像的内容; 清单3-2中的代码片段绘制了 PDF 页面的缩小图像,给出了一个示例。 请注意,代码在调用 CGContextDrawPDFPage 之前翻转图形上下文,以将绘制的图像与 UIKit 的默认坐标系对齐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Listing 3-2  Drawing to a bitmap context using Core Graphics functions
// Other code precedes...

CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
pdfScale = self.frame.size.width/pageRect.size.width;
pageRect.size = CGSizeMake(pageRect.size.width * pdfScale, pageRect.size.height * pdfScale);
UIGraphicsBeginImageContextWithOptions(pageRect.size, YES, pdfScale);
CGContextRef context = UIGraphicsGetCurrentContext();

// First fill the background with white.
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,pageRect);
CGContextSaveGState(context);

// Flip the context so that the PDF page is rendered right side up
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

// Scale the context so that the PDF page is rendered at the
// correct size for the zoom level.
CGContextScaleCTM(context, pdfScale,pdfScale);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
UIImage *backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];

// Other code follows...

如果您更喜欢完全使用 Core Graphics 来绘制位图图形上下文,则可以使用 CGBitmapContextCreate 函数创建位图上下文并将图像内容绘制到其中。 完成绘制后,调用 CGBitmapContextCreateImage 函数以从位图上下文中获取 CGImageRef 对象。 您可以直接绘制 Core Graphics 图像或使用它来初始化 UIImage 对象。 完成后,在图形上下文中调用 CGContextRelease 函数。

Generating PDF Content

ps:暂时没用到。不看

Printing

ps:暂时没用到。不看

Improving Drawing Performance

在任何平台上绘图都是一项相对昂贵的操作,优化绘图代码应始终是开发过程中的重要一步。 下面列出了几种确保绘图代码尽可能最佳的技巧。 除了这些提示以外,还应始终使用可用的性能工具来测试代码并删除热点和冗余。

  • Draw minimally (在每个更新周期中,应该只更新视图中实际更改的部分。 如果使用 UIView 的 drawRect: 方法来绘制图形,请使用传递给该方法的更新矩形来限制绘图的范围。 对于 OpenGL 绘图,您必须自己跟踪更新。)
  • Call setNeedsDisplay: judiciously (如果正在调用 setNeedsDisplay: , 请始终花时间计算需要重绘的实际区域。 不要只传递包含整个视图的矩形。 此外,不要调用 setNeedsDisplay: 除非确实需要重绘内容。 如果内容实际上没有更改,请不要重绘。)
  • Mark opaque views as such (合成内容不透明的视图比合成部分透明的视图要少得多。 要使视图不透明,视图的内容不得包含任何透明度,并且视图的 opaque 属性必须设置为YES。)
  • Reuse table cells and views during scrolling (应该不惜一切代价避免在滚动期间创建新视图。 花时间创建新视图减少了更新屏幕的可用时间,从而导致不均匀的滚动行为。)
  • Reuse paths by modifying the current transformation matrix (通过修改当前转换矩阵,可以使用单个路径在屏幕的不同部分上绘制内容。 有关详细信息,请参阅”#Using Coordinate Transforms to Improve Drawing Performance#”。)
  • Avoid clearing the previous content during scrolling (默认情况下, UIKit 在调用其drawRect: 方法之前会清除视图的当前上下文缓冲区以更新同一区域。 如果要响应视图中的滚动事件,则在滚动更新期间反复清除此区域可能会非常昂贵。 要禁用该行为,可以将 clearsContextBeforeDrawing 属性中的值更改为 NO 。)
  • Minimize graphics state changes while drawing (更改图形状态需要底层图形子系统的工作。 如果您需要绘制使用类似状态信息的内容,请尝试将该内容绘制在一起以减少所需的状态更改次数。)
  • Use Instruments to debug your performance (核心动画工具可以帮助您发现应用中的绘图性能问题。 特别是:Flash Updated Regions 可让您轻松查看视图的哪些部分实际更新。 Color Misaligned Images 可帮助您查看对齐不良的图像,从而导致图像模糊和性能不佳。有关更多信息,请参阅 Instruments User Guide 中的 Measuring Graphics Performance in Your iOS Device。)

Supporting High-Resolution Screens In Views

针对 iOS SDK 4.0 及更高版本构建的应用程序,需要准备好在具有不同屏幕分辨率的设备上运行。 幸运的是,iOS可以轻松支持多种屏幕分辨率。 处理不同类型屏幕的大部分工作都是由系统框架完成的。 但是,应用仍需要做一些工作来更新基于光栅的图像(raster-based images),并且根据应用程序,可能需要执行其他工作以利用可用的额外像素。

有关此主题的重要背景信息,请参阅”#Points Versus Pixels#”。

Checklist for Supporting High-Resolution Screens

要为具有高分辨率屏幕的设备更新应用程序,您需要执行以下操作:

  • 为应用包中的每个图像资源提供高分辨率图像,如”#Updating Your Image Resource Files#”中所述。
  • 提供高分辨率应用和文档图标,如”#Updating Your App’s Icons and Launch Images#”中所述。
  • 对于基于矢量的形状和内容,请像以前一样继续使用自定义 Core Graphics 和 UIKi t绘图代码。 如果要为绘制的内容添加额外的细节,请参阅点”#Points Versus Pixels#”以获取有关如何执行此操作的信息。
  • 如果使用 OpenGL ES 进行绘制,请确定是否要选择加入高分辨率绘图并相应地设置图层的比例因子,如”#Drawing High-Resolution Content Using OpenGL ES or GLKit#”中所述。
  • 对于自定义图像,请修改图像创建代码以将当前比例因子考虑在内,如”#Drawing to Bitmap Contexts and PDF Contexts#”中所述。
  • 如果应用使用核心动画,请根据需要调整代码以补偿比例因子,如”#Accounting for Scale Factors in Core Animation Layers#”中所述。

Drawing Improvements That You Get for Free

iOS 中的绘图技术提供了大量支持,无论底层屏幕的分辨率如何,都可以帮助您使渲染内容看起来很好:

  • 标准 UIKit 视图(文本视图,按钮,表视图等)可以在任何分辨率下自动呈现。
  • 基于矢量的内容(UIBezierPath , CGPathRef , PDF)自动利用任何其他像素来为形状渲染更清晰的线条。
  • 文本以更高的分辨率自动呈现。
  • UIKit 支持自动加载图像的高分辨率变体(@2x)。

如果您的应用仅使用原生绘图技术进行渲染,那么支持更高分辨率的屏幕,您需要做的唯一就是提供高分辨率版本的图像

Updating Your Image Resource Files

在 iOS 4 中运行的应用现在应该为每个图像资源包含两个单独的文件。 一个文件提供给定图像的标准分辨率版本,第二个文件提供同一图像的高分辨率版本。 每对图像文件的命名约定如下:

1
2
标准: <ImageName> <device_modifier>.<filename_extension>
高分辨率: <ImageName> @2x <device_modifier>.<filename_extension>

每个名称的 部分指定文件的通常名称和扩展名。 部分是可选的,包含字符串ipad或iphone。 如果要为 iPad 和 iPhone 指定不同版本的图像,请包含其中一个修饰符(modifier)。 为高分辨率图像添加 @2x 修饰符是新的,让系统知道图像是标准图像的高分辨率变体。

要点:修饰符的顺序至关重要。 如果错误地将 @2x 放在设备修饰符之后, iOS 将无法找到该图像。

在创建图像的高分辨率版本时,请将新版本放在应用包中与原始版本相同的位置。

Loading Images into Your App

UIImage 类处理将高分辨率图像加载到应用程序所需的所有工作。 创建新图像对象时,使用相同的名称来请求图像的标准版本和高分辨率版本。 例如,如果有两个名为 Button.png 和 Button@2x.png 的图像文件,则可以使用以下代码来请求您的按钮图像:

1
UIImage *anImage = [UIImage imageNamed:@"Button"];

注意:在 iOS 4 及更高版本中,可以在指定图像名称时省略文件扩展名。

在具有高分辨率屏幕的设备上 imageNamedimageWithContentsOfFile:initWithContentsOfFile: 方法会自动查找所请求图像的版本,其名称中包含 @2x 修饰符。如果找到一个,则会加载该图像。如果您未提供给定图像的高分辨率版本,则图像对象仍会加载标准分辨率图像(如果存在)并在绘图期间对其进行缩放

加载图像时, UIImage 对象会根据图像文件的后缀自动将大小和比例属性设置为适当的值。对于标准分辨率图像,它将 scale 属性设置为 1.0 ,并将图像的大小设置为图像的像素尺寸。对于文件名中带有 @2x 后缀的图像,它将scale属性设置为 2.0 ,并将 width 和 height 值减半以补偿比例因子。这些减半的值与您需要在逻辑坐标空间中用于渲染图像的基于点的尺寸正确关联

注意:如果使用 Core Graphics 创建图像,请记住 Quartz 图像没有明确的比例因子,因此它们的比例因子假定为1.0。 如果要从 CGImageRef 数据类型创建 UIImage 对象,请使用 initWithCGImage:scale:orientation: 来执行此操作。 该方法允许您将特定比例因子与 Quartz 图像数据相关联。

在绘制过程中, UIImage 对象会自动考虑其比例因子。 因此,只要在应用包中提供正确的图像资源,用于渲染图像的任何代码都应该相同。

Using an Image View to Display Multiple Images

如果应用程序使用 UIImageView 类为突出显示或动画显示多个图像,则分配给该视图的所有图像必须使用相同的比例因子。 可以使用图像视图显示单个图像或为多个图像设置动画,还可以提供高光图像。 因此,如果您为其中一个图像提供高分辨率版本,那么所有图像也必须具有高分辨率版本。(ps: 分辨率都得相同。)

Updating Your App’s Icons and Launch Images

除了更新应用的自定义图像资源外,还应该为应用的图标和启动图像提供新的高分辨率图标。 更新这些图像资源的过程与所有其他图像资源相同。 创建图像的新版本,将 @2x 修饰符字符串添加到相应的图像文件名,并在处理原始图像时处理图像。 例如,对于应用程序图标,将高分辨率图像文件名添加到应用程序的 Info.plist 文件的 CFBundleIconFiles 键。

有关为应用程序指定图标和启动图像的信息,请参阅App Programming Guide for iOS中的App-Related Resources

Drawing High-Resolution Content Using OpenGL ES or GLKit

ps:暂时没用到。不看

Loading Images

出于功能和美学的原因,图像是 app 用户界面的普遍元素。 它们可以成为应用程序跟其他应用不同的关键因素。

应用程序使用的许多图像(包括启动图像和应用程序图标)都作为文件存储在应用程序的主程序包中。 可以启动特定于设备类型的图像和图标(iPad与iPhone和iPod touch),并针对高分辨率显示进行了优化。 可以在 App Programming Guide for iOSAdvanced App TricksApp-Related Resources中找到这些捆绑图像文件(bundled image files) 的完整描述。 “#Updating Your Image Resource Files#”讨论了使图像文件与高分辨率屏幕兼容的调整。

此外, iOS 还支持使用 UIKit 和 Core Graphics 框架加载和显示图像。 如何确定用于绘制图像的类和函数取决于您打算如何使用它们。 但是,只要有可能,建议使用 UIKit 类在代码中表示图像。 表C-1列出了一些使用方案以及处理它们的建议选项。

Scenario Recommended usage
Display an image as the content of a view 使用 UIImageView 类显示图像。 此选项假定视图的唯一内容是图像。 但仍然可以在图像视图的顶部层叠其他视图以绘制其他控件或内容。
Display an image as an adornment for part of a view 使用 UIImage 类加载并绘制图像。
Save some bitmap data into an image object 可以像在”#Creating New Images Using Bitmap Graphics Contexts#”中描述的 UIKit 函数或核心图形函数来执行此操作。
Save an image as a JPEG or PNG file 从原始图像数据创建 UIImage 对象。 调用UIImageJPEGRepresentationUIImagePNGRepresentation 函数来获取NSData对象,并使用该对象的方法将数据保存到文件中。

System Support for Images

UIKit 框架以及 iOS 的低级系统框架提供了创建,访问,绘图,编写和操作图像的广泛可能性。

UIKit Image Classes and Functions

UIKit框架有三个类和一个协议,它们以某种方式与图像相关:

  • UIImage (此类的对象表示 UIKit 框架中的图像。 可以从几个不同的源创建它们,包括文件和 Quartz 图像对象。该类的方法能够使用不同的混合模式(blend modes)和不透明度值(opacity values)将图像绘制到当前图形上下文。 UIImage 类自动处理任何所需的转换,例如应用适当的比例因子(考虑高分辨率显示),并且在给定 Quartz 图像时,修改图像的坐标系以使其与默认的坐标系匹配 UIKit(y起源位于左上角))
  • UIImageView (此类的对象是显示单个图像或为一系列图像设置动画的视图。 如果图像是视图的唯一内容,请使用 UIImageView 类而不是绘制图像。)
  • UIImagePickerController 和 UIImagePickerControllerDelegate (此类和协议为应用程序提供了获取用户提供的图像(照片)和视频的方法。 该类提供和管理用户界面,用于选择和拍摄 照片和视频。 当用户选择照片时,它会将选定的 UIImage 对象传递给委托,该委托必须实现协议方法。)

除了这些类之外,UIKit 还声明了可以使用图像执行各种任务的函数:

  • Drawing into an image-backed graphics context. (UIGraphicsBeginImageContext 函数创建一个屏幕外位图图形上下文。可以在此图形上下文中绘制,然后从中提取 UIImage 对象。 (有关其他信息,请参阅”#Drawing Images#”。))
  • Getting or caching image data. (每个 UIImage 对象都有一个可以直接访问的支持 Core Graphics 图像对象(CGImageRef) (“backing Core Graphics image object (CGImageRef)”)。 然后,可以将 Core Graphics 对象传递给 Image I/O 框架以保存数据。 还可以通过调用 UIImagePNGRepresentationUIImageJPEGRepresentation 函数将 UIImage 对象中的图像数据转换为 PNG 或 JPEG 格式。 然后,可以访问数据对象中的字节,并可以将图像数据写入文件。)
  • Writing an image to the Photo Album on a device. (调用 UIImageWriteToSavedPhotosAlbum 函数,传入UIImage对象,将该图像放入设备上的相册中。)

“#Drawing Images#”标识了使用这些UIKit类和函数时的场景。

可以使用除 UIKit 之外的多个系统框架来创建,访问,修改和写入图像。 如果发现无法使用 UIKit 方法或函数完成某个与图像相关的任务,则这些较低级别框架之一的功能可能能够执行您想要的操作。 其中一些功能可能需要 Core Graphics 图像对象(CGImageRef)。 可以通过 CGImage 属性访问支持 UIImage 对象的 CGImageRef 对象。

注意:如果存在 UIKit 方法或函数来完成给定的图像相关任务,则应使用它而不是任何相应的低级函数。

相关的框架有

  • Core Graphics framework
  • Image I/O framework (读取和写入各种图片格式。支持快速的编码、解码图片,图片元数据,图片缓存)
  • Assets Library

Supported Image Formats

表C-2列出了 iOS 直接支持的图像格式。 在这些格式中,PNG 格式是最适合在您的应用中使用的格式。 通常,UIKit 支持的图像格式与 Image I/O 框架支持的格式相同。
(ps: 具体的看官方文档吧。)

Maintaining Image Quality

为用户界面提供高质量的图像应该是设计的首要任务。图像提供了一种显示复杂图形的合理有效方式,应该在适当的地方使用。为应用创建图片时,请牢记以下准则:

  • Use the PNG format for images. (使用PNG格式的图像。 PNG 格式提供无损图像内容,这意味着将图像数据保存为 PNG 格式然后将其读回会产生完全相同的像素值。 PNG 具有优化的存储格式,旨在更快地读取图像数据。它是 iOS 的首选图像格式。)
  • Create images so that they do not need resizing. (创建图像,以便它们不需要调整大小。如果您计划使用特定大小的图像,请确保以该大小创建相应的图像资源。不要创建更大的图像并将其缩小以适应,因为缩放需要额外的 CPU 周期并需要插值。如果需要以可变大小显示图像,请包含不同大小的图像的多个版本,并从相对接近目标大小的图像缩小。)
  • Remove alpha channels from opaque PNG files. (从不透明的 PNG 文件中删除 Alpha 通道。如果PNG 图像的每个像素都是不透明的,则删除 Alpha 通道可以避免混合包含该图像的图层。这大大简化了图像的合成,并提高了绘图性能。)