0%

Understanding UIKit Rendering(View Programming Guide for iOS)

WWDC

Understanding UIKit Rendering 主要讲了显示青蛙的一个 demo ,讲了 round rotate anti-aliased shadow 等效果,先讲了之前做的为什么会错误,后面讲了正确的做法,这个 session 比较简单,相关内容,后续关于渲染的 session 都有讲到。

UIView and CALayer

在 iOS 中,每个视图(UIView)都由相应的图层(CALayer)对象支持,被称为图层支持的视图(layer-backed view)。对于不同的绘制要求,我们可以用不同类型的 layer 。 layer 决定视图在屏幕哪里显示。

Geometry

frame 包含视图的最小矩形。 不管进行了 transform or scale or rotation 等操作, bound 都不会变,变的是 frame 。因为 frame 不是 layer 的存储属性(store) , 而是计算(computed)属性 , 而是由其他两个属性计算而来的(center(基于父坐标系)、bound(基于自己的坐标系))。 可以有下面的两张图来表示,黄色字体代表 bound , 白色字体代表 frame 。

rotation
rotation

所以,在 view 中, frame 是由 bounds.size、center、transform 组成的。而在 layer 中,则由 bounds.size、position、affineTransform、transform(这是三维的变换)、anchorPoint 组成的。

bound 也是可以改变的,用来显示大区域视图的部分内容, bound 区域外面的内容是不会渲染的,用 clipsToBounds 属性控制。

绘制的内容可以来自于:

  • view.layer.contents (这是 iOS 的底层绘制机制,很少几乎不会直接设置,所以 UIKit 有提供下面两种方法)
  • UIImageView (推荐用)
  • drawRect:

UIImageView vs. drawRect: (主要指内存消耗)

  • Stretch: UIImageView 0 内存消耗, UIImageView 放大的时候还可以动画, drawRect 也可以,但是看起来可能会很糟糕。
  • Tile: 尽管 UIImageView 让 GPU 去处理最终的 tile 从而填充整个屏幕,会有一点点消耗,但是相对于 drawRect: 来说,这一点开销还是可以忽略的。

CATransaction and when views get rendered

有一种情况,你可能改变了 frame 和 transform ,但它们并没有同时显示,而是只显示了其中一个变化。想知道为什么,就得知道视图什么时候开始渲染。 设置属性的时候就会在 runloop 中创建隐式事务(implicit transaction), 此时隐式事务是打开的,随后你设置的相关改变都会被放入这个隐式事务,然后一起提交,一旦 runloop 开启下个循环, Core animation 就会提交这个隐式事务,然后就能在屏幕上显示了。

有的时候,你可能设置了属性,但是没有显示,可能是阻塞了主线程, runloop 下个循环没法开启,所以 Core animation 没有提交事务,也就没渲染成功。

有隐式事务,那肯定就有显式(implicit)事务,我们尽量使用隐式事务,除非是做动画或者想控制时间。

我们 lldb 调试时候,经常会看到
CA::Transaction::commint

Quality and Performance

发现渲染问题,让想要提高它的最普遍的方法是寻找 offscreen rendering, 然后避免它。

Avoid Offscreen Rendering

不是离屏渲染(offscreen rendering)的情况下, core animation 会在屏幕上开始渲染最底层的视图,然后渲染相应的子视图(从下到上)
如果一个视图的是离屏渲染的,那么它的子视图也是离屏渲染的。离屏渲染大概有两个问题吧

  • 额外的 memory, 创建 off-screen buffer
  • 额外的时间,在 off-screen 和 on-screen 切换

如果 core animation 每一帧都得做这样的事情,是很昂贵的。
ps: 离屏渲染优化详解:实例示范+性能测试 有讲很多优化的例子。

Layer Rasterization

图层光栅化,可以减少离屏渲染的影响,它会缓存渲染的位图,减少了 off-screenon-screen 切换的时间。当然它只适应于上下两帧内容一样的情况,如果不一样,还是跟之前一样,还是会有性能问题。但是,如果你没有离屏渲染,而去使用光栅化,会损耗性能,因为它不能重用缓存,每一帧还是在 off-screenon-screen 切换。(切记,出现问题的时候,一定要记录当前的数据,然后再去做调试优化,再拿当前的数据和之前的作对比。)

Clipping and masking

cornergradient mask 也会引发离屏渲染。可以用以下方法:

  • [CALayer contentsRect], 使用大图片时可以考虑用一下,比 corner 性能要好。
  • [UIView drawRect:], 提前渲染好。
  • Transparent overlay, 遮罩来处理。

Group opacity

在父视图上设置,父视图和它的子视图都有同样的 alpha 效果,它也会产生离屏渲染。可以用以下方法:

  • drawRect: 方法中提前渲染好。
  • 光栅化, shouldRasterize = YES

Shadows

设置阴影方法不当也会引起离屏渲染。

不要用

  • [CALayer shadowOffset]
  • [CALayer shadowColor]

而用

  • [CALayer shadowPath]
  • CoreGraphics shadows

Edge anti-aliasing

当位图的分辨率明显低于设备的分辨率时,会出现锯齿状边缘。为了使得看起来更加平滑(smooth)。我们可以对围绕形状轮廓的像素使用不同的颜色。通过以这种方式混合颜色,形状看起来光滑。
A comparison of aliased and anti-aliasing drawing

Apple Document

View Programming Guide for iOS

About Windows and Views

在 iOS 下,我们用 View 和 Window 在屏幕上面呈现 App 的内容, Window 不会直接呈现可视化的内容,只是为程序的 View 提供一个 container 载体
(ps: 所以,用 Xcode Debug 展示最下面是黑色的,还有用 Xcode 创建新的工程,single view controller 也是黑色的)

At a Glance

每一个 App 至少有一个 window 和一个用于显示其内容的 View, UIKit 为我们提供系统控件。当然,当系统控件满足不了我们需求的时候,我们可以自定义控件并管理绘图和事件处理

Views Manage Your Application’s Visual Content

view 或者它的子类对象在 App 的 Window 上管理着 App 的矩形区域(Views are responsible for drawing content, handling multitouch events, and managing the layout of any subviews.)。 drawing 涉及到 Core Graphics, OpenGL ES, UIKit 等绘制技术在矩形区域里面绘制物体、图片、文本等。 视图通过手势识别器(gesture recognizers)或者直接处理触摸事(touch events)来响应其矩形区域中的触摸事件。 在 View 的层级里面,父视图有责任去定位和调整子视图的位置,并且能够动态的改变它们,使它们自适应旋转、动画等一些改变条件。 我们可以假想多个 View 堆积成我们的用户界面,每个 View 都呈现着不同的内容。

Windows Coordinate the Display of Your Views

Window 处理着 App 整个的用户界面(它与 view 或者 vc 一起工作,去管理可视化 view 的交互和改变),大多数情况下,App 的 Window 不会改变。当 window 创建好后,它不会改变,只通过改变 View 现实。每一个应用至少有一个 Window 在主屏幕呈现 App 的用户界面。当外部显示器连接到设备时, App 会创建第二个 Window 去显示内容。

Animations Provide the User with Visible Feedback for Interface Changes

animation 在当前 View 的层级上为用户提供可视的变化。可以改变 View 的透明度transparency、位置、大小、背景颜色或者其他属性。 Core Animation layer object, 能够更好的做好动画。(ps: Core Animation Programming Guide 有讲。)

The Role of Interface Builder

用户界面就是一个 App 去形象化的组织和配置 Window 和 View 。(ps: 这里 Apple 又推荐用 xib 了)

View and Window Architecture

view 和 window 用来呈现内容和处理与界面的交互,自定义的视图也得继承自它们。理解它们这些基础控件是怎么操作的会很有好处。

View Architecture Fundamentals

视图对象在屏幕上定义矩形区域,并处理该区域中的绘图和触摸事件。 视图还可以充当其他视图的父级,并协调这些视图的位置和大小调整。 UIView 类完成了管理视图之间这些关系的大部分工作,但我们也可以根据需要自定义默认行为。

视图与核心动画层结合使用,以处理视图内容的渲染和动画。 UIKit 中的每个视图都有一个图层对象(通常是CALayer类的一个实例)支持,该图层对象管理视图的后备存储并处理与视图相关的动画。 我们执行的大多数操作都应该通过 UIView 接口。 但是,在需要更多控制视图的渲染或动画行为的情况下,可以通过其图层执行操作。

背后的 layer 对象是 Core Animation 的渲染对象,最终用于管理屏幕上实际位图的硬件缓冲区

Core Animation layer objects 的使用对性能有重要的影响。 view 对象的实际绘图代码越少越好,当你绘制的时候, Core Animation 将会缓存结果,以便后面能够尽量的重用。重用已经渲染后的内容能够消除昂贵的绘制周期去更新视图,重用在动画的时候也非常重要。(ps: Core Animation layer object 的相关东西,Core Animation Programming Guide 有讲。)

View Hierarchies and Subview Management

View 除了提供自己的内容以外,还能作为其他 View 的容器,这就是 subView superView 的关系。 subView 的内容能够掩盖全部或部分它 superView 的内容,即有层级关系

如果 subview 部分透明,那么 subview 、superview 它们两个的内容就会混合在一起显示在屏幕上面。

视图有一个有序的数组来存储它的子视图,后添加的在最顶层。改变父视图的大小会使子视图的大小和位置改变(这里 autoresize 等属性就能处理这些事情)。视图的层级结构也决定了 App 去响应事件,当 touch 触摸事件发生在某个特定的视图上,系统就会直接发送一个包含 touch information 的 event object 给该视图去处理。它是向上传递的,如果父视图处理不了,就丢给它的父视图,一直沿着响应链 responder chain 传递,一直会传递到 application object ,不过它一般会丢弃它(这里就涉及到了响应链的知识)。

The View Drawing Cycle

视图类采取按需绘图模型来展示内容(即按需处理绘制)。当视图首次出现在屏幕上时,系统会要求它绘制其内容,系统会捕获此内容的快照,然后用这个快照去展示视图的视觉效果。如果你不改变视图的内容,那么视图的绘制代码就不会再次执行。这个快照图片(snapshot image)对于所涉及的视图的大部分操作是可以重用的。如果确实更改了内容,则通知系统视图已更改。然后,视图重复绘制视图并捕获新结果的快照的过程。

当视图内容改变的时候,你不用直接去重新绘制这些改变。你只需调用 setNeedsDisplay 或者 setNeedsDisplayInRect 系统方法,这些方法会告诉系统在下一次循环会重新绘制。系统会等到这次循环结束才会开始任何绘制操作。 这个延时中,你有机会做各种事情,比如:使多个视图无效,在层次结构中添加或删除视图,隐藏视图,调整视图大小以及重新定位视图,这些所有的改变会在同时反应出来。(ps: 就是 implicit transaction)

注意:改变视图的几何构造 geometry(在 view.h 文件里面,搜索 geometry 就会看到 geometry 的相关属性 frame、bounds、center 等等)不会自动使系统去重绘视图的内容。 contentMode 属性决定了 geometry 变化时,视图的内容怎么改变。大部分模式会在视图的边界里面拉伸或者重新放置已经存在的 snapshot, 不会创建一个新的

当渲染视图内容时,实际绘制过程会根据视图及其配置而有所不同。系统视图通常实现私有绘图方法来呈现其内容。 这些相同的系统视图通常会公开可用于配置视图实际外观的接口。 对于自定义视图子类,通常会覆盖视图的 drawRect: 方法,并使用该方法绘制视图的内容。还有其他方法可以提供视图的内容,例如直接设置底层的内容(layer.contents),但是覆盖 drawRect: 方法是最常用的技术。(ps: Core Animation Programming Guide 中的 “Providing a Layer’s Contents” 有提到三种方法设置内容。)

有关如何为自定义视图绘制内容的详细信息,请参阅”#Implementing Your Drawing Code#”。

Content Modes

每个视图都有一个内容模式,用于控制视图如何重新利用其内容以响应视图几何中的更改,以及是否重新利用其内容。 首次显示视图时,它会像往常一样呈现其内容,并在底层位图(bitmap)中捕获结果。 之后,对视图几何体的更改并不总是会导致重新创建位图。 相反, contentMode 属性中的值确定是否缩放位图以适应新边界,还是仅固定到视图的一个角或边缘。(ps: 最终都是位图去展示的)

content mode 作用于改变视图的 frame bounds transform 属性。见下图

Content mode comparisons

内容模式适用于重新利用视图内容,但是特别希望自定义视图在缩放和调整大小操作期间重绘时,还可以将内容模式设置为 UIViewContentModeRedraw 。将视图的内容模式设置为此值,会强制系统调用视图的 drawRect: 方法以响应几何体更改。通常,应尽可能避免使用此值,并且当然不应将其与标准系统视图一起使用

Stretchable Views

可以将视图的一部分指定为可伸缩的,以便在视图大小发生变化时,只会影响可伸展部分中的内容。 通常将可伸展区域用于按钮或其他视图,其中视图的一部分定义了可重复(repeatable pattern)的模式。 指定的可伸展区域可以允许沿视图的一个或两个轴拉伸。 当然,沿两个轴拉伸视图时,视图的边缘也必须定义可重复的图案以避免变形(distortion)。 下图显示了这种失真如何在视图中显现出来。 原理就是,复制每个视图的原始像素的颜色以填充较大视图中的相应区域(ps: 跟抗锯齿差不多哈)。

Stretching the background of a button

可以使用 contentStretch 属性指定视图的可伸展区域。 此属性接受一个矩形,它是标准值,其值的范围为 0.0-1.0 。 在拉伸视图时,系统会将这些标准化值乘以视图的当前边界和比例因子,以确定需要拉伸哪个或哪些像素。 使用标准化值可以减少每次视图边界更改时更新 contentStretch 属性的需要

视图的内容模式(content mode)也在确定视图的可伸展区域(stretchable area)的使用方式方面发挥作用。 可伸缩区域仅在内容模式导致视图的内容缩放时使用。 这意味着仅使用 UIViewContentModeScaleToFill UIViewContentModeScaleAspectFit 和 UIViewContentModeScaleAspectFill 内容模式支持可伸展视图。 如果指定将内容固定到边缘或角落的内容模式则视图将忽略可伸展区域(因此实际上不会缩放内容,ps: 其实就是上面三种模式以外的内容模式,即内容模式大致分为两种,一种是缩放去适应视图,另外一种只是将内容移动到视图的一个边角。)。

注意:当指定为视图背景时,创建伸缩的 UIImage 对象时推荐用 contentStretch 属性。 stretchable view 整个被用在 Core Animation layer, 能够提供更好的性能。

(ps: contentStretch 能够指定拉伸的区域,但是只适用于 ScaleToFill、ScaleAspectFit、ScaleAspectFill 这三种内容模式。)

Built-In Animation Support

One of the benefits of having a layer object behind every view is that you can animate many view-related changes easily. (在每个视图后面都有一个图层对象的好处之一是,可以轻松地为许多与视图相关的更改设置动画。)

用内置的动画支持,你只需做两件事,告诉 UIKit 你想要执行一个动画,然后改变相关属性的值

  • Tell UIKit that you want to perform an animation.
  • Change the value of the property.

视图的以下属性可以用于动画

  • frame—Use this to animate position and size changes for the view.
  • bounds—Use this to animate changes to the size of the view.
  • center—Use this to animate the position of the view.
  • transform—Use this to rotate or scale the view.
  • alpha—Use this to change the transparency of the view.
  • backgroundColor—Use this to change the background color of the view.
  • contentStretch—Use this to change how the view’s contents stretch.

下降到 layer 层,可以更多的控制时间以及动画属性。可以参考

View Geometry and Coordinate Systems

UIKit 默认的坐标系统是从左上角开始并且轴向右、向下延伸。坐标值用的是浮点类型的,它能使你更加精确的计算出位置和内容而不用管底层屏幕分辨率。当然每个view、window 都有属于自己的 local coordinate system ,即有的时候我们需要将在 view 上的坐标转换(convert)到 controller.view 上面显示(视图和窗口有提供相关的方法去转换,这点得注意)。

要点:一些 iOS 技术定义的坐标系统跟 UIKit 不同, Core Graphics 和 OpenGL ES 使用左下角为起始点, y 轴是向上的。

The Relationship of the Frame, Bounds, and Center Properties

frame 在其 superview 中指定位置大小, bounds 在自己的坐标系统中指定大小, center 在 superview 中的中心点。(ps: frame 是包裹视图内容的最小矩形区域。 session 2011.121 P23)

frame 和 center 主要是操作当前 view 的几何结构,主要是操作视图的大小和位置。如果你只改变视图的位置,推荐用 center ,尤其是在缩放或旋转的时候,即使已将缩放或旋转因子添加到视图的变换中,center 属性中的值也始终有效。 对于 frame 属性中的值也是如此,如果视图的变换不等于 identity 变换,则该属性被视为无效
bounds 一般用于绘制的时候。

当 frame 改变时,center、bounds 会改变;center 改变时,frame 会改变;bounds 改变时,frame 会改变。 clipsToBounds 属性设置当 subview 超出 superview 时是否被裁剪,如果不被裁剪,那么超出部分不会响应事件。

Relationship between a view's frame and bounds

ps: 还可以参考 http://stackoverflow.com/questions/1210047/cocoa-whats-the-difference-between-the-frame-and-the-bounds

Coordinate System Transformations

坐标系转换提供了一种快速轻松地更改视图(或其内容)的方法。 仿射变换(affine transform)是一种数学矩阵,它指定一个坐标系中的点如何映射到不同坐标系中的点。 我们可以将仿射变换应用于整个视图,以更改视图相对于其父视图的大小,位置或方向。 还可以在绘图代码中使用仿射变换对各个渲染内容执行相同类型的操作。 因此,如何应用仿射变换取决于上下文

  • 要修改整个视图,请在视图的 transform 属性中修改仿射变换。
  • drawRect: 方法中修改视图的特定内容,修改活动图形上下文(active graphics context)关联的仿射变换。

当然不要使用此属性对视图进行永久性更改,例如在其 superview 的坐标空间中修改其位置或调整视图大小。 对于这种类型的更改,应该修改视图的框架矩形。

注意:当改变视图的 transform 属性时,所有的变化只是相对于视图的中心点(center)进行的。

当前变换矩阵(current transformation matrix, CTM)是在任何给定时间使用的仿射变换。 在操作整个视图的几何体时,CTM 是存储在视图的 transform 属性中的仿射变换。 在 drawRect: 方法中,CTM 是与活动图形上下文关联的仿射变换。

每个子视图的坐标系建立在其祖先的坐标系上。 因此,当修改视图的 transform 属性时,该更改会影响视图及其所有子视图。但是,这些更改仅影响屏幕上视图的最终呈现。因为每个视图都绘制其内容并相对于其自己的边界布置其子视图,所以它可以在绘制和布局期间忽略其父视图的变换。可以参考下图

Rotating a view and its content

要点:如果视图的 transform 属性不是 identity 变换时,则该视图的 frame 属性的值是未定义的,必须忽略。 将变换应用于视图时,必须使用视图的 bounds 和 center 属性来获取视图的大小和位置。 任何子视图的 frame 仍然有效,因为它们相对于视图的 bounds 。

有关如何在绘图期间使用变换来定位内容的信息,请看 Drawing and Printing Guide for iOS

Points Versus Pixels

在 iOS 中,使用称为点(point)的单位的浮点值指定所有坐标值和距离。 一个点的可测量大小因设备而异,并且在很大程度上是无关紧要的。 关于点的主要理解是它们为绘图提供了固定的参考框架。

虽然用户坐标空间中的坐标有时会直接映射到设备屏幕上的像素,但绝不能认为是这种情况。相反,应该始终记住以下内容:

One point does not necessarily correspond to one pixel on the screen. 跟分辨率来的。

在设备级别,在视图中指定的所有坐标(points)必须在某个时刻转换为像素(pixels)。 然而,用户坐标空间中的点到设备坐标空间 中的像素的映射通常由 系统 处理。 UIKit 和 Core Graphics 都使用主要基于矢量(vector-based)的绘图模型,其中所有坐标值都使用点指定。 因此,如果使用 Core Graphics 绘制曲线,则无论底层屏幕的分辨率如何,都使用相同的值指定曲线。

当需要使用图像或其他基于像素的技术(如OpenGL ES)时,iOS 可以帮助管理这些像素。 对于作为应用程序包中的资源存储的静态图像文件,iOS 定义了以不同像素密度(pixel densities)指定图像以及加载与当前屏幕分辨率最匹配的图像的转换约定。 视图还提供有关当前比例因子(scale factor)的信息,以便可以手动调整任何基于像素的绘图代码以适应更高分辨率的屏幕。 有关处理不同屏幕分辨率的基于像素的内容的技术,请参阅
Supporting High-Resolution Screens In Views

The Runtime Interaction Model for Views

每当用户与用户界面交互时,或者自己的代码以编程方式更改某些内容时, UIKit 内部都会发生复杂的事件序列来处理该交互。在该序列中的特定点,UIKit 会调用视图类,并让他们有机会代表应用程序做出响应。 了解这些标注点对于了解视图系统非常重要。 下图显示了以用户触摸屏幕开始并以图形系统更新屏幕内容作为响应而结束的事件的基本顺序。 对于任何以编程方式启动的操作,也会发生相同的事件序列。

Figure 1-7 UIKit interactions with your view objects

  1. 用户触摸屏幕;
  2. 硬件将触摸事件报告给 UIKit framework;
  3. UIKit 包装 touch 成 UIEvent 对象,然后将它分发给相应的视图;(这就响应链了,更多细节,可以查看Event Handing Guide for iOS
  4. 视图的事件处理代码会响应事件(当然,这些操作取决于我们自己)。 例如:
    1. 改变视图或者它子视图的属性:frame、bounds、alpha 等等;
    2. 调用 setNeedsLayout 方法将视图(或其子视图)标记为需要布局更新;
    3. 调用 setNeedsDisplay 或 setNeedsDisplayInRect: 方法将视图(或其子视图)标记为需要重绘;
    4. 通知控制器改变一些数据;
  5. 如果视图的 geometry 因任何原因而发生了更改, UIKit 将根据以下规则更新其子视图:
    1. 如果它的子视图配置了autoresizing,那么 UIKit 会处理,详情请看 Handling Layout Changes Automatically Using Autoresizing Rules
    2. 如果视图实现了 layoutSubviews 方法,UIKit 会调用它;可以在自定义视图中覆盖此方法,并使用它来调整任何子视图的位置和大小。(ps: 这里列举了滚动显示大图各个细小部分的例子)。
  6. 如果任何视图的任何部分被标记为需要重绘,UIKit 会要求视图重绘。 对于显式定义 drawRect: 方法的自定义视图, UIKit 会调用该方法。 对此方法的实现应该尽快重绘视图的指定区域,而不是其他任何内容。 此时不要进行其他布局更改,也不要对应用程序的数据模型进行其他更改。 此方法的目的是更新视图的可视内容。 标准系统视图通常不实现 drawRect: 方法,而是在此时管理其绘图
  7. 任何更新的视图都与应用程序的其他可见内容合成,并发送到图形硬件。
  8. 图形硬件将渲染的内容传输到屏幕。

(ps: 这里可以对照 session 2014.419 P5 来仔细看看)

注意:前面的更新模型主要适用于使用标准系统视图和绘图技术的应用程序。 使用 OpenGL ES 进行绘图的应用程序通常配置单个全屏视图并直接绘制到关联的 OpenGL ES 图形上下文。 在这种情况下,视图仍然可以处理触摸事件,但由于它是全屏的,因此不需要布置子视图。 有关使用OpenGL ES的更多信息,请参阅OpenGL ES Programming Guide

在上面的一组步骤中,自定义视图的主要集成点是

  1. 事件处理方法:
    1. touchesBegan:withEvent:
    2. touchesMoved:withEvent:
    3. touchesEnded:withEvent:
    4. touchesCancelled:withEvent:
  2. layoutSubviews方法
  3. drawRect: 方法

这些是最常被覆盖的视图方法,但可能不需要覆盖所有这些方法。 如果使用手势识别器来处理事件,则无需覆盖任何事件处理方法。 同样,如果视图不包含子视图或其大小未更改,则没有理由覆盖 layoutSubviews 方法。 最后,只有当视图的内容可以在运行时更改并且使用 native 技术(如 UIKit 或 Core Graphics)进行绘制时,才需要 drawRect: 方法。

同样重要的是要记住,这些是主要的集成点,但不是唯一的集成点。 UIView类的几个方法被设计为子类的覆盖点。 我们应该查看UIView类参考中的方法描述,以了解哪些方法可能适合在自定义实现中覆盖。

Tips for Using Views Effectively

Important: Before optimizing your drawing code, you should always gather data about your view’s current performance. Measuring the current performance lets you confirm whether there actually is a problem and, if there is, gives you a baseline measurement against which you can compare future optimizations.
要点:优化绘图代码之前,你应该始终收集关于视图当前性能的数据。衡量当前的性能,以便知道问题在哪,并且给一个基准用于未来优化的对比。类似于 60fps 吧

Views Do Not Always Have a Corresponding View Controller

在 App 中, view 和 viewcontroller 一对一的关系是很少见的。所以这里得 明确 viewcontroller 的职责:改变 view 在屏幕上的位置、将 view 从屏幕上面移除、释放内存、旋转 view 等。如果避开这些行为会引起你的App行为错误或者在出乎意料的情况。

Minimize Custom Drawing

减少自定义的绘图。只有当前系统不能提供你所需要的外表和能力时才自定义绘图。(如果当前视图组合能够完成你的效果时,你完全可以组合)

Take Advantage of Content Modes

内容模式可最大限度地 缩短重绘视图所花费的时间 。 默认情况下,视图使用 UIViewContentModeScaleToFill 内容模式,该模式缩放视图的现有内容以适合视图的框架矩形。 你可以根据需要更改此模式的不同方式去调整内容,但如果可以,则应避免使用 UIViewContentModeRedraw 内容模式。 无论哪种内容模式生效,都可以通过调用 setNeedsDisplay 或 setNeedsDisplayInRect: 来强制视图重绘其内容

Declare Views as Opaque Whenever Possible

UIKit 使用每个视图的 opaque 属性来确定视图是否可以优化合成操作。 对于自定义视图,将此属性的值设置为 YES 会告诉 UIKit 它不需要在视图后面呈现任何内容。较少的渲染可以提高绘图代码的性能,并且通常会受到鼓励。 当然,如果将 opaque 属性设置为 YES ,则视图必须使用完全不透明的内容完全填充其边界矩形

Adjust Your View’s Drawing Behavior When Scrolling

滚动可以在很短的时间内产生大量的视图更新。 如果视图的绘图代码未正确调整,则视图的滚动性能可能会很低(sluggish:迟钝)。 不要试图确保视图的内容始终保持原始状态,而是考虑在滚动操作开始时更改视图的行为。 例如,可以临时降低渲染内容的质量,或在滚动过程中更改内容模式。 滚动停止后,可以将视图返回到先前的状态,并根据需要更新内容。 (ps: 尽量减少滚动时的完全渲染)。 (ps: VVeboTableViewDemo 有讲到)

ps: 滚动的时候减少绘图等相关耗时、耗资源的操作,滚动结束后再恢复先前状态。

Do Not Customize Controls by Embedding Subviews

虽然从技术上讲可以将子视图添加到标准系统控件 - 从 UIControl 继承的对象 - 但是永远不应该以这种方式自定义它们。 支持自定义的控件,它本身中明确且文档说明的接口来实现。 例如,UIButton 类包含用于设置按钮的标题和背景图像的方法。通过在按钮内部嵌入自定义图像视图或标签来绕过这些方法,可能会导致应用程序现在或在将来某个时候因为按钮的实现发生更改而表现不正确(ps: 别到 UIButton 上添加 image 和 label 来完成你的需求,因为 UIButton 已经有内置的支持了;要多看系统控件接口说明)。

Windows

每个至少需要一个 window(UIWindow类的实例对象)。有的甚至需要多个 window。一个 window 有几个责任

  • It contains your application’s visible content.(包含App的可视内容)
  • It plays a key role in the delivery of touch events to your views and other application objects.(在touch event分发中起到非常重要的角色)
  • It works with your application’s view controllers to facilitate orientation changes.(与App的控制器合作,方便方向变化)

在 iOS 中,window 没有 title bars, close box 或者其他可见的装饰。window 一直都是一个或者多个 view 的黑色容器(所以在Xcode的View debug中,最下层是黑色的)。同样的,App不会用新的window去展示新的内容,只需改变 window frontmost 最前面的 view 即可。大多数 App 在它的生命周期只会创建使用一个 window, 该 window 横跨设备的整个屏幕,在 App 生命周期用 nib 或者代码创建的。当然 App 也可以创建一个附加的window(external display)去显示其他的内容,比如来电话了,当然了其他所有的 window 一般都是系统创建的。

Tasks That Involve Windows

对于许多应用程序,应用程序与其窗口交互的唯一时间是它在启动时创建窗口。 但是,可以使用应用程序的窗口对象执行一些与应用程序相关的任务:

  • Use the window object to convert points and rectangles to or from the window’s local coordinate system. (坐标转换,特定view本地坐标系统于window对应坐标系统的转换,详情请参阅”#Converting Coordinates in the View Hierarchy#”)
  • Use window notifications to track window-related changes. (用window通知跟踪window相关的变化,详情请参阅”#Monitoring Window Changes#”)

Creating and Configuring a Window

当需要其他 window 时,应该按需创建。只要程序一启动就创建window,创建配置 window 不是一件耗资源的事情。当然,如果你的App一启动就直接进入后台,你应该让它到前台时才显示(visible)。

Creating Windows in Interface Builder

要点:在 Interface Builder 中创建窗口时,建议在属性检查器中启用“启动时全屏”选项。 如果未启用此选项且窗口小于目标设备的屏幕,则某些视图将不会接收到触摸事件。 这是因为窗口(像所有视图一样)不会在其边界矩形之外接收触摸事件。 由于默认情况下视图不会剪切到窗口的边界,因此视图仍然显示为可见但它们不能接收到事件。 在启动时启用全屏选项可确保窗口的大小适合当前屏幕。 ps: bounds clip touch

Creating a Window Programmatically

创建的 window 大小应该和屏幕大小一样。你不应该为了适应状态栏或者其他的元素而改变window的大小。状态栏一直浮在 window 的上面,所有你要做的是使 window 上面的 view 的大小去适应状态栏,如果你 viewcontroller 的话,viewcontroller 会根据状态栏自动改变它 view 的大小。

Adding Content to Your Window

一般是 viewcontroller 的 view 去承载所有需要展示的内容。使用 root view object 简化改变界面的过程,如果要展示新内容,你只需替换它的root view object

用view、普通viewcontroller的view作为window的root view,需要减去状态栏的高度,而用tabbar or navigation or split-view controller的view提供的则不需要,因为这些viewcontroller会自动去改变。(ps: 主要讲了状态栏高度的相关问题)

Changing the Window Level

每一个window都会有一个windowLevel属性去决定与其他相关window的位置。 normal window level指示window呈现App相关的内容。高一点的window level是显示在App内容上面的,如系统system status或者alert messages。尽管你自己可以改变window level,但是系统会帮你做。比如,当你想要显示或隐藏状态栏,或者显示alert view时,系统会创建需要的window去显示这些内容。(ps: alert view的window层级比normal要高,写弹出popup view的时候可以注意一下)。

Monitoring Window Changes

UIWindowDidBecomeVisibleNotification、UIWindowDidBecomeHiddenNotification window 显示、隐藏。这个跟App在前台、后台没有关系,只跟App的内容是否显示有关系。

UIWindowDidBecomeKeyNotification、UIWindowDidResignKeyNotification 表面哪个是主 window。
可帮助应用程序跟踪哪个窗口是关键窗口 - 即哪个窗口当前正在接收键盘事件和其他非触摸相关事件。 触摸事件被传递到发生触摸的窗口,而没有相关坐标值的事件将被传递到应用程序的关键窗口。 一次只有一个窗口是关键窗口。(Q: 发生到主window上面的事件会丢弃掉?还有 没有相关坐标的事件? A: 这里应该是指异常事件,如果触摸事件没被响应,不也会被丢弃)

Displaying Content on an External Display

外部显示器显示内容,应该说的是视屏输出吧,即将手机的视屏投影到其他大屏幕上面观看(爱奇艺 mac app 有这个功能)。

Handling Screen Connection and Disconnection Notifications

Configuring a Window for an External Display

Configuring the Screen Mode of an External Display

Views

view object是 App 与用户交互的主要途径,它有许多的责任。这里列举一些:

  • 布局和管理子视图:根据父视图定义自己的默认大小行为;管理它的子视图;在需要的时候布局子视图的大小位置;够将自己的坐标系统转换成其他view或者window的坐标系统。
  • 绘图和动画:在它的矩形区域绘图;一些属性能够动画过渡到新的值。
  • 事件处理:能够接收 touch events; 参与响应链。

Creating and Configuring View Objects

Creating View Objects Using Interface Builde

Creating View Objects Programmatically

Setting the Properties of a View

  • alpha,hidden,opaque (这些属性改变view的的不透明度。alpha、hidden是直接影响view的不透明度。opaque属性告诉系统是否复合view,设为YES能够消除不必要的复合操作从而提升性能)
  • bounds,frame,center,transform (这些影响view的大小位置。如果当前的变换不是恒等的变换,那么frame属性是不确定的,并且会被忽略。(ps: 这应该就是有的动画里面需要定义一个唯一的字符串的意思吧,YYKit的CALayer+YYAdd文件里面有,类似于[self setValue:@(v) forKeyPath:@”transform.rotation.y”])
  • autoresizingMask, autoresizesSubviews (这些属性影响view以及其subviews自定改变大小的行为。autoresizingMask控制view响应父视图bounds的改变。autoresizesSubviews控制它的subviews是否resized。)
  • contentMode, contentStretch, contentScaleFactor (这些属性影响 view 内容的渲染,contentMode,contentStretch 属性决定当视图的宽高变化时它的内容怎样变化。 contentScaleFactor 只用于在高分辨率下你需要自定义重绘视图的情况)
  • gestureRecognizers, userInteractionEnabled, multipleTouchEnabled, exclusiveTouch (这些影响view的触摸事件处理)
  • backgroundColor, subviews, drawRect: method, layer, (layerClass method) (这些属性帮助你管理view的实际内容,为了更多先进的内容,你可以直接操作 view Core Animation layer 。如果想指定 view 整个不同类型的 layer,必须重载 layerClass 方法。)

Tagging Views for Future Identification

tag值执行搜索是在运行时,它比遍历view的层级查找速度要快。 要搜索标记视图,请使用UIView的 viewWithTag: 方法。 此方法执行接收器及其子视图的深度优先搜索。 它不搜索视图层次结构的父视图或其他部分。 因此,从层次结构的根视图调用此方法将搜索层次结构中的所有视图,但是从特定子视图调用它仅搜索视图的子集。

Creating and Managing a View Hierarchy

管理 View 的层级也非常重要,view的组织影响App的展示以及App怎么响应事件。

Adding and Removing Subviews

bringSubviewToFront:, sendSubviewToBack:, or exchangeSubviewAtIndex:withSubviewAtIndex: 等方法管理层级,比直接移除后重新添加效率要高(ps: autolayout 的时候也推荐用 active ro deactive 而不是移除和添加)。

One place where you might add subviews to a view hierarchy is in the loadView or viewDidLoad methods of a view controller. If you are building your views programmatically, you put your view creation code in the loadView method of your view controller. Whether you create your views programmatically or load them from a nib file, you could include additional view configuration code in the viewDidLoad method. 可以在视图控制器的 loadView 或 viewDidLoad 方法中,在视图层次结构中添加子视图。 如果以编程方式构建视图,则将视图创建代码放在视图控制器的 loadView方法中。 无论是以编程方式创建视图还是从nib文件加载视图,都可以在viewDidLoad方法中包含其他视图配置代码。 (ps: loadView 的官方介绍)。

Sample Code -> UIKit Catalog (iOS): Creating and Customizing UIKit Controls

重要提示:父视图会自动保留持有其子视图,因此在嵌入子视图后,可以安全地释放该子视图。(ps: 我记得当时有一篇关于 addSubview 内存操作的博客VIEWCONTROLLER中的UIVIEW PROPERTY要设置为WEAK还是STRONG 。原来 removeFromSuperview 在subview从superview remove之前会 autorelease)。

当添加view到其他view上的时候,UIKit会通知子视图和父视图关于它们的变化。所以我们在自定义 view 的时候得充分利用 willMoveToSuperview:, willMoveToWindow:, willRemoveSubview:, didAddSubview:, didMoveToSuperview, or didMoveToWindow 方法。view还有一个window属性
用来表明view当前展示内容的window。由于视图层次结构中的根视图没有父视图,因此其 superview 属性设置为nil。 对于当前在屏幕上的视图,窗口对象是视图层次结构的根视图。

Hiding Views

隐藏view,我们可以设置hidden属性为yes或者alpha属性为0.0。 hidden view不会接收系统touch event。但是它参与view的自动改变大小以及与当前view层级相关的布局操作。所以当你想移除view后在某个时候又想让它出现时 用hidden属性非常方便。(ps: 所以现在代码中 判断它是否有subview 然后又addSubview的代码 可以用hidden来处理?)。

重要说明:如果隐藏当前是第一个响应者的视图,则视图不会自动 resign 其第一个响应者状态。 针对第一响应者的事件仍然被传递到隐藏视图,为防止这种情况发生,应该强制视图在隐藏第一个响应者状态时 resign 。

hidden 不是动画属性,如果想要动画隐藏/显示的话,请用 alpha 。

Locating Views in a View Hierarchy

这里有两种方法从view的层级里面定位到某个view:

  • 定义一个指针指向相关的view,其实就是定义一个属性或者私有的成员变量。
  • 用 tag

tag是减少硬编码以及更加动态、灵活的解决方案。这里还举例了:如果想要保存已经当前在App可见的view,可以将可见view的tag写进文件,这比archive归档要简单,尤其是在跟踪那些当前view是可见的。当App随后加载的时候,重新创建你的view,然后用保存的tag list来设置每个view的显示,这样App又返回到以前的层级状态了。(ps: 这里应该牵扯了 app 状态保存的情况)。

Translating, Scaling, and Rotating Views

每个view都有一个相关联的仿射矩阵,你可以用于翻转、缩放、旋转view的内容。 view的transforms属性影响view的最终渲染效果,通常用于滚动、动画或者其他的视觉效果。

当你给view添加多个转换,添加的顺序是非常重要的。比如:先旋转后翻转和先翻转后旋转 效果是不同的。rotation旋转跟中心点(center point)有关系,scaling会改变大小但是跟中心点没有关系。

Converting Coordinates in the View Hierarchy

  • convertPoint:fromView:
  • convertRect:fromView:
  • convertPoint:toView:
  • convertRect:toView:

这些方法提供了到或者从本地坐标系的转换。 convert…:FromView: 相关方法是从其他view的坐标系中转换到当前view坐标系中的方法;convert…:toView: 是从当前view的坐标系统的位置转换到其他view的坐标系统的位置。 如果指定某个view为nil,那么转换的时候处理的就是 contain 包含该 view 的 window 。

当然window也有相应的转换方法:

  • convertPoint:fromWindow:
  • convertRect:fromWindow:
  • convertPoint:toWindow:
  • convertRect:toWindow:

在旋转视图中转换坐标时,a view在旋转后view的坐标和 outer view(parent)的坐标是不同的。在parent的坐标是包含自己坐标的最小矩阵,从图可以看出相对于outer view的坐标轴的方向线是平行的。

Converting values in a rotated view

Adjusting the Size and Position of Views at Runtime

无论view的大小怎么改变,它的subview的大小位置必须相应的改变。

Being Prepared for Layout Changes

当下列事件发生时,布局会改变:

  • The size of a view’s bounds rectangle changes. (当view的bounds发生改变时。)
  • An interface orientation change occurs, which usually triggers a change in the root view’s bounds rectangle.(当界面方向发生变化时,它通常会触发root view的bounds发生变化。)
  • The set of Core Animation sublayers associated with the view’s layer changes and requires layout.(与view layer相关的Core Animation sublayers发生改变 并且要求布局时。)
  • Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.(调用view的 setNeedsLayout 或者 layoutIfNeeded 方法时,App会强制布局。)
  • Your application forces layout by calling the setNeedsLayout method of the view’s underlying layer object.(当调用view下面layer的 layoutIfNeeded 方法时,App会强制布局。)

Handling Layout Changes Automatically Using Autoresizing Rules

当view的大小发生变化时,它里面嵌入的subviews的大小位置一般会发生改变去适应view的新大小。view的 autoresizesSubviews 属性决定它的subview是否resize。当 autoresizesSubviews 为 YES时,并且每个subview的 autoresizingMask 会改变它们的大小位置。(即 parentview 的 autoresizesSubviews 属性和 subview 的 autoresizingMask 属性是相对应的。)
处理好 autoresizingMask 属性对于我们手动改变布局很重要。(如果不指定它的常量值时 当parentview的size发生变化时 它们始终维持着固定的值)

Figure 3-4  View autoresizing mask constants

Important: If a view’s transform property does not contain the identity transform, the frame of that view is undefined and so are the results of its autoresizing behaviors.(重要:如果view的 transform 属性不包含 identity transform,那么view的frame是不确定的,因为是它自动改变大小的结果。)

当automatic autoresizing适应已经在view上面处理后,UIKit会返回然后给每个view一个机会手动做相应的调整去适应它的superview. 请看下一节 Tweaking the Layout of Your Views Manually。(ps: 这根 autolayout 是一样的原理,它也会调用 layoutsubviews 。)

Tweaking the Layout of Your Views Manually

当view的大小发生改变时,UIKit处理完subviews的autoresizing行为后,会调用 layoutSubviews 方法去做手动的处理。 你可以在自定义的view时做响应的处理当autoresizing行为不能产生你想要的结果时。实现这个方法 可以做下面的事情:

  • Adjust the size and position of any immediate subviews.(改变任意直接子视图的大小位置)
  • Add or remove subviews or Core Animation layers.(添加或移除 subviews 或 Core Animation layers)
  • Force a subview to be redrawn by calling its setNeedsDisplay or setNeedsDisplayInRect: method. (通过调用 setNeedsDisplay 或者 setNeedsDisplayInRect 方法强制一个 subview 去重绘)

app通常手动布局子视图的一个地方是实现大型可滚动区域。 因为为其可滚动内容提供单个大视图是不切实际的,所以应用程序通常实现包含许多较小的tile视图的根视图。 每个图块表示可滚动内容的一部分。 当滚动事件发生时,根视图调用其 setNeedsLayout 方法来启动布局更改。 然后,layoutSubviews 方法根据发生的滚动量重新定位切片视图。 当tile从视图的可见区域滚动出来时,layoutSubviews 方法会将tile移动到传入边缘(icoming edge),替换其中的内容。
(这个跟tableview的处理方法差不多哈,可以处理用scrollView实现tableView的效果,详情可参看 Sample Code: ScorllViewSuite)。

当写布局代码时,务必根据下面途径来测试你的代码:

  • 手机方向发生变化。
  • 打电话进来了,注意状态栏的变化。

Modifying Views at Runtime

App接受用户的输入,然后响应输入去调整用户界面。App可能会重新排列,改变大小位置,显示或隐藏它的view,或者重新加载一组新的view。会在一些地方或者相关的途径去执行一些操作:

  • view controller: 在显示view之前创建view,在不需要的时候销毁它们;调整view的大小位置,显示或者隐藏一些views;管理可编辑的内容;
  • animation blocks: 在不同组之间做动画转换时,你在某个动画的block里面显示或隐藏它们;改变view的各种属性以实现特殊的效果;
  • Other ways: touch event or gesture occur Event Handling Guide for iOS; 用户与滚动视图交互时,一个大的滚动区域可能会隐藏显示相关的 tile subviewsScroll View Programming Guide for iOS; 键盘事件发生时,相应相关的变化 Text Programming Guide for iOS

(ps: 这个guide里面有相关字体的知识,可以看看这篇文章iOS Typography: Stop Saying “No” to Designers)

Interacting with Core Animation Layers

每个view对象都有一个专门的Core Animation layer 管理view的呈现和动画在屏幕上。也就是说内容都是展示在layer上面的。尽管你能够操作 view 对象,当然你也可以在需要的时候直接操作相应的layer对象。

Changing the Layer Class Associated with a View

(view关联的layer类型,在view创建后不能更改)layer的默认类型是CALayer,而唯一的改变layer类型的途径是继承它,继承相应的方法,然后返回不同的值。例如:你想要在大的滚动区域中平铺内容,你可以使用 CATiledLayer (又是 tiling large scrollable area)。

每个 view 的 layerClass 方法是在 initialization 初始化方法之前返回类型去创建它layer对象。此外,view通常把自己作为它layer对象的代理。在这点上,view拥有它的layer,并且view和layer之间的关系一定不能改变。同样的,你不能将同一个view作为其他任何的layer对象的代理。改变拥有关系和view的代理关系会引起绘制问题以及潜在的崩溃。

Embedding Layer Objects in a View

如果更喜欢主要使用图层对象而不是视图,则可以根据需要将自定义图层对象合并到视图层次结构中。 自定义图层对象是不属于视图的CALayer的任何实例。 通常以编程方式创建自定义图层,并使用Core Animation例程(Core Animation routines)合并它们。 自定义图层不会接收事件或参与响应者链,但会根据核心动画规则自行绘制并响应其父视图或图层中的大小更改。(ps: 这就是 layer 和 view 的区别之一,不会响应事件)

Defining a Custom View

如果标准系统视图不能完全满足我们的需要,我们可以自定义视图。 自定义视图可以完全控制应用程序内容的外观以及如何处理与该内容的交互。

Checklist for Implementing a Custom View

自定义视图的工作是呈现内容并管理与该内容的交互。 但是,自定义视图的成功实现不仅仅涉及绘制和处理事件。 以下清单包括在实现自定义视图时可以覆盖的更重要的方法(以及我们可以提供的行为):

  • 定义合适的初始化方法,
    • 手动创建,重载 initWithFrame: 或者自定义一个初始化方法;
    • 打算从nib加载的话,重载 initWithCoder: 方法
  • 实现 dealloc 方法来处理任何自定义数据的清理。
  • 处理自定义绘图的话,覆盖 drawRect: 方法并在那里进行绘制。
  • 设置视图的 autoresizingMask 属性以定义其自动调整行为。
  • 如果视图类管理一个或多个完整子视图,请执行以下操作:
    • 在视图的初始化序列中创建这些子视图;
    • 在创建时设置每个子视图的 autoresizingMask 属性;
    • 如果子视图需要自定义布局,请覆盖 layoutSubviews 方法并在那里实现布局代码;
  • 要处理基于触摸的事件,请执行以下操作:
    • 使用 addGestureRecognizer: 方法将任何合适的手势识别器附加到视图。
    • 对于想自己处理触摸的情况,请覆盖 touchesBegan:withEvent:touchesMoved:withEvent:touchesEnded:withEvent:touchesCancelled:withEvent: 方法。 (请记住,应该始终覆盖 touchesCancelled:withEvent: 方法,无论覆盖哪些其他与触摸相关的方法。)
  • 如果希望视图的打印版本与屏幕版本不同,请实现 drawRect:forViewPrintFormatter: 方法。

还可以设置 UIView 已有的属性, contentMode contentStretch 来控制绘制操作。

Initializing Your Custom View

nib 的话,用 NSCoding 协议中的 initWithCoder: 方法来处理的。即使你实现了NSCoding协议,IB 还是不知道你 custom view 的属性,所以不会 encode 这些属性到 nib 文件。所以,你需要在 initWithCoder: 方法里面执行各种初始化代码是视图它处于一个已知的状态。当然也可以事先 awakeFromNib 方法去执行一些额外的初始化工作。

Implementing Your Drawing Code

如果你要实现自定义的绘制,你需要去重载 drawRect: 方法去绘制。自定义绘制是推荐的最后一种手段。通常情况下,推荐使用其他的views去展示内容

drawRect: 方法只做一件事:绘制内容。你不能在这个方法里面更新你APP的数据结构或者执行其他与绘制无关的任务。配置好环境后就开始绘制内容,尽可能快的退出该方法。如果你的 drawRect: 方法需要频繁的调用,你必须做任何能够优化绘制代码的事情并且每次尽可能的绘制一点点。
在调用drawRect:方法之前,UIKit 会配置 view 的基础绘制环境。明确来说,它会创建一个 graphics content, 并且自适应坐标系统,为了匹配 view 的可视化区域而裁剪相关区域。所以,当 drawRect: 方法调用后,你就可以开始绘制了。你可以用 UIGraphicsGetCurrentContext 方法获取 current graphics context 的指针。

要点:当前图形上下文(current graphics context)仅在对视图的drawRect:方法的一次调用期间有效。 UIKit可能会为每个后续调用此方法创建不同的图形上下文,因此不应尝试缓存该对象并在以后使用它。

如果我们知道视图的绘图代码始终覆盖具有不透明内容的视图的整个表面,则可以通过将视图的opaque属性设置为YES来提高系统性能。 将视图标记为不透明时,UIKit会避免绘制位于视图后面的内容。 这不仅减少了绘图所花费的时间,而且最大限度地减少了将视图与其他内容合成所必须完成的工作。 但是,只有在知道视图的内容完全不透明时,才应将此属性设置为YES。 如果视图无法保证其内容始终不透明,则应将该属性设置为NO。 (ps: 自己确定完全不透明的情况下,将 opaque 置为 NO, 提升性能)

另一种提高绘图性能的方法,特别是在滚动期间,是将视图的 clearsContextBeforeDrawing 属性设置为NO。 当此属性设置为YES时,UIKit会在调用方法之前使用透明黑色自动填充要由 drawRect: 方法更新的区域。 将此属性设置为NO可消除该填充操作的开销,但会给应用程序带来负担,将填充传递给带有内容的 drawRect: 方法的更新矩形。(“Setting this property to NO eliminates the overhead for that fill operation but puts the burden on your application to fill the update rectangle passed to your drawRect: method with content.”) (ps: clearsContextBeforeDrawing 置为 NO, 避免填充带来的开销,但是会传递给 drawRect: 方法)

Responding to Events

注意:UIView的动画方法通常在动画正在进行时禁用触摸事件。 可以通过适当地配置动画来覆盖此行为。 有关执行动画的更多信息,请参阅”#Animations#”。

当你处理触摸事件的时候,UIKit用 hitTest:withEvent:pointInside:withEvent: 方法去决定该触摸事件是否发生在指定view的边界内。虽然很少需要去重载这些方法,你仍然可以用它去实现某些自定义view的自定义触摸事件。例如:你可以重载该方法去防止某些subview去处理触摸事件。

Cleaning Up After Your View

不要在这个方法里面做其他事情,只做释放资源的事情。

Animations

动画在用户界面切换不同状态时提供流动的视觉转换效果。在iOS动画中,一般会重置view的位置,改变它的大小,从view曾经中移除,隐藏它们。你可能会用动画传达反馈给用户或者实施有趣的视觉效果。 在iOS中,创建复杂的动画不需要编写任何绘图代码。 本章中描述的所有动画技术都使用 Core Animation 提供的内置支持。 所要做的就是触发动画并让Core Animation处理各个帧的渲染。

What Can Be Animated?

在UIKit中,用UIView对象就能执行动画。
UIView支持animatable动画的属性有: frame、bounds、center、transform、alpha、backgroundcolor、contentstretch。
如果你想执行更加复杂的动画,可以使用Core Animation 用layer来创建动画。因为layer错综复杂联系在一起,改变view的layer能够改变view自己。使用Core Animation,你可以动画下面类型的改变:

  • The size and position of the layer
  • The center point used when performing transformations
  • Transformations to the layer or its sublayers in 3D space
  • The addition or removal of a layer from the layer hierarchy
  • The layer’s Z-order relative to other sibling layers
  • The layer’s shadow
  • The layer’s border (including whether the layer’s corners are rounded)
  • The portion of the layer that stretches during resizing operations
  • The layer’s opacity
  • The clipping behavior for sublayers that lie outside the layer’s bounds
  • The current contents of the layer
  • The rasterization behavior of the layer

注意:如果视图承载自定义图层对象(即没有关联视图的图层对象),则必须使用 Core Animation 对其进行动画处理。

更多的核心动画,请看 About Core AnimationCore Animation Cookbook

Animating Property Changes in a View

为了对UIView类的属性进行动画处理,必须将这些更改包装在动画块中。在iOS 4以及以后,用block-based来执行动画,在iOS3.2及以前用begining和end来处理,当然这两种技术支持同样的配置以前提供相同的动画执行,但是block-based更加优秀。

Starting Animations Using the Block-Based Methods

所以这里只讲block-based类型的动画。主要是以下这三个方法:

  • animateWithDuration:animations:
  • animateWithDuration:animations:completion:
  • animateWithDuration:delay:options:animations:completion:
1
2
3
4
[UIView animateWithDuration:1.0 animations:^{
firstView.alpha = 0.0;
secondView.alpha = 1.0;
}];

执行上述代码时,会立即在另一个线程上启动指定的动画,以避免阻塞当前线程或应用程序的主线程

要点:当一个属性的动画正在进行时,更改该属性的值不会停止当前动画。相反,当前动画将继续并动画显示刚刚分配给属性的新值。

“Whether touch events are delivered to views while the animations are in progress” (ps: 动画进行中,还能接受其他事件?如果真要这么处理,该怎么玩)

Nesting Animation Blocks

大部分内嵌动画用自己的配置选项与父类(外面)同时开始执行。默认情况下,nested animations继承parent animations的时间与动画选项,但是这些选项在必须情况下是可以重载的。例如(UIViewAnimationOptionOverrideInheritedCurve UIViewAnimationOptionOverrideInheritedDuration)

Implementing Animations That Reverse Themselves

可以用 repeat count 结合创建一个可逆的动画,考虑为了repeat count 指定一个非整形的值。对于autoreversing自动翻转的动画,某个完整的动画周期涉及到从原值到新值,然后再返回到原值。对于重复的动画,如果希望动画以新值结束,则向重复计数添加 0.5 会导致动画完成以新值结束所需的额外半周期。如果不包括此半步,则动画将设置为原始值的动画,然后快速捕捉到新值,这可能不是想要的视觉效果。

Creating Animated Transitions Between Views

视图过渡(view transition)会帮助你在你的view的层级里面隐藏关于添加、移除、隐藏、显示views的突然操作。即这些操作方法时不会一下子就发生了,会有个一个过渡效果。 你会用view transtions实现下列类型的改变:

  • 改变 subview 是否可见
  • 将一个 subview 替换成另一个 subview

要点:视图过渡不应与视图控制器启动的过渡 混淆,例如模态视图控制器的显示或将新视图控制器推送到导航堆栈。 视图转换仅影响视图层次结构,而视图控制器转换也会更改活动视图控制器。因此,对于视图转换,在启动转换时处于活动状态的视图控制器在转换完成时也保持活动状态。

Changing the Subviews of a View

在iOS 4及更高版本中,使用 transitionWithView:duration:options:animations:completion: 方法来启动视图的过渡动画。 在传递给此方法的动画块中,通常设置为动画的唯一更改是与显示,隐藏,添加或删除子视图相关联的更改。 将动画限制到此集合中,允许视图创建视图前后两个版本的快照图像,并在两个图像之间设置动画,这样更有效。 但是,如果需要动画其他改变,则可以在调用方法时包含 UIViewAnimationOptionAllowAnimatedContent 选项。包含该选项可防止视图直接创建快照并直接动画所有更改。 (ps: 快照 snapshot)

Replacing a View with a Different View

这个技术能让你用一些标准的过渡效果快速的呈现一个新的内容。如果你不选择先移除后插入的方式,而是用隐藏的效果来处理,则可以用 UIViewAnimationOptionShowHideTransitionViews 关键字。

Linking Multiple Animations Together

completion handler 可以将多个animation连接起来,它们是相继发生的,而不是同时发生。 当然你还可以用nested animations使用不同的delay延时因素,因为nested animation是同时发生的,只能控制它们的delay延时时间来达到相继发生的效果。

Animating View and Layer Changes Together

APP能够在需要的时候自由的混合 view-based 和 layer-based 的动画代码,但是配置动画属性的过程取决于谁拥有这个layer。改变 view-owned layer 跟改变 view 是同样的效果,并且你添加到 layer 属性的动画能够反应在当前 view-based animations 里面。同样的,它不适用于你自己创建的layer,custom layer会忽略掉view-based animations 参数,默认情况下适用 Core Animation 参数代替。 用 Core Animation 动画 layers 可以创建一个 CABasicAnimation 对象或者 CAAnimation 的子类。 用 Core Animation 做动画会更加容易,可以查看 Core Animation Programming Guide