iPhone OS编程指南(三)

来源:互联网 发布:lr推荐算法 编辑:程序博客网 时间:2024/06/05 16:18
原文地址:iPhone OS编程指南(三)作者:若水一叶

浮点数学运算的考虑

iPhone–OS设备上的处理器有能力在硬件上处理浮点数计算。如果您目前的程序使用基于软件的定点数数学库进行计算,则应该考虑对代码进行修改,转向使用浮点数数学库。典型情况下,基于硬件的浮点数计算比对应的基于软件的定点数计算快得多。

重要提示:当然,如果您的代码确实广泛地使用浮点数计算,请记住不要使用-mthumb选项来编译代码。Thumb选项可以减少代码模块的尺寸,但是也会降低浮点计算代码的性能。

 

减少电力消耗

移动设备的电力消耗一直是个问题。iPhoneOS的电能管理系统保持电能的方法是关闭当前未被使用的硬件功能。此外,要避免CPU密集型和高图形帧率的操作。您可以通过优化如下组件的使用来提高电池的寿命:

  • CPU

  • Wi-Fi和基带(EDGE, 3G)无线信号

  • Core Location框架

  • 加速计

  • 磁盘

您的优化目标应该是以尽可能有效的方式完成大多数的工作。您应该总是采用Instruments和Shark工具对应用程序的算法进行优化。但是,很重要的一点是,即使最优化的算法也可能对设备的电池寿命造成负面的影响。因此,在写代码的时候应该考虑如下的原则:

  • 避免需要轮询的工作,因为轮询会阻止CPU进入休眠状态。您可以通过NSRunLoop或者NSTimer类来规划需要做的工作,而不是使用轮询。

  • 尽一切可能使共享的UIApplication对象的idleTimerDisabled属性值保持为NO。当设备处于不活动状态一段时间后,空闲定时器会关闭设备的屏幕。如果您的应用程序不需要设备屏幕保持打开状态,就让系统将它关闭。如果关闭屏幕给您的应用程序的体验带来负面影响,则需要通过修改代码来消除那些影响,而不是不必要地关闭空闲定时器。

  • 尽可能将任务合并在一起,以便使空闲时间最大化。每隔一段时间就间歇性地执行部分任务比一次性完成相同数量的所有任务开销更多的电能。间歇性地执行任务会阻止系统在更长时间内无法关闭硬件。

  • 避免过度访问磁盘。举例来说,如果您需要将状态信息保存在磁盘上,则仅当该状态信息发生变化时才进行保存,或者尽可能将状态变化合并保存,以避免短时间频繁进行磁盘写入操作。

  • 不要使屏幕描画速度比实际需求更快。从电能消耗的角度看,描画的开销很大。不要依赖硬件来压制应用程序的帧率,而是应该根据程序实际需要的帧率来进行帧的描画。

  • 如果你通过UIAccelerometer类来接收常规的加速计事件,则当您不再需要那些事件时,要禁止这些事件。类似地,请将事件传送的频率设置为满足应用程序需要的最小值。更多信息请参见“访问加速计事件”部分。

您向网络传递的数据越多,就需要越多的电能来进行无线发射。事实上,访问网络是您所能进行的最耗电的操作,您应该遵循下面的原则,使网络访问最小化:

  • 仅在需要的时候连接外部网络,不要对服务器进行轮询。

  • 当您需要连接网络时,请仅传递完成工作所需要的最少数据。请使用紧凑的数据格式,不要包含可被简单忽略的额外数据。

  • 尽可能快地以群发(inburst)方式传递数据包,而不是拉长数据传输的时间。当系统检测到设备没有活动时,就会关闭Wi-Fi和蜂窝无线信号。您的应用程序以较长时间传输数据比以较短时间传输同样数量的数据要消耗更多的电能。

  • 尽可能通过Wi-Fi无线信号连接网络。Wi-Fi耗电比基带无线少,是推荐的方式。

  • 如果您通过Core Location框架收集位置数据,则请尽可能快地禁止位置更新,以及将位置过滤器和精度水平设置为恰当的值。CoreLocation通过可用的GPS、蜂窝、和Wi-Fi网络来确定用户的位置。虽然CoreLocation已经努力使无线信号的使用最小化了,但是,设置恰当的精度和过滤器的值可以使CoreLocation在不需要位置服务的时候完全关闭硬件。更多信息请参见“获取用户的当前位置”部分。

代码的优化

和iPhone OS一起推出的还有几个应用程序的优化工具。它们中的大部分都运行在Mac OSX上,适合于调整运行在仿真器上的代码的某些方面。举例来说,您可以通过仿真器来消除内存泄露,确保总的内存开销尽可能小。借助这些工具,您还可以排除代码中可能由低效算法或已知瓶颈引起的计算热点。

在仿真器上进行代码优化之后,还应该在设备上用Instruments程序进行进一步优化。在实际设备上运行代码是对其进行完全优化的唯一方式。因为仿真器运行在Mac OS X上,而运行Mac OSX的系统具有更快的CPU和更多的可用内存,所以其性能通常比实际设备的性能好很多。在实际设备上用Instruments跟踪代码可能会发现额外的性能瓶颈,您需要进行优化。

更多有关Instruments的使用信息,请参见Instruments用户指南


窗口和视图

窗口和视图是为iPhone应用程序构造用户界面的可视组件。窗口为内容显示提供背景平台,而视图负责绝大部分的内容描画,并负责响应用户的交互。虽然本章讨论的概念和窗口及视图都相关联,但是讨论过程更加关注视图,因为视图对系统更为重要。

视图对iPhone应用程序是如此的重要,以至于在一个章节中讨论视图的所有方面是不可能的。本章将关注窗口和视图的基本属性、各个属性之间的关系、以及在应用程序中如何创建和操作这些属性。本章不讨论视图如何响应触摸事件或如何描画定制内容,有关那些主题的更多信息,请分别参见“事件处理”“图形和描画”部分。

什么是窗口和视图?

和Mac OS X一样,iPhoneOS通过窗口和视图在屏幕上展现图形内容。虽然窗口和视图对象之间在两个平台上有很多相似性,但是具体到每个平台上,它们的作用都有轻微的差别。

UIWindow的作用

和Mac OSX的应用程序有所不同,iPhone应用程序通常只有一个窗口,表示为一个UIWindow类的实例。您的应用程序在启动时创建这个窗口(或者从nib文件进行装载),并往窗口中加入一或多个视图,然后将它显示出来。窗口显示出来之后,您很少需要再次引用它。

在iPhoneOS中,窗口对象并没有像关闭框或标题栏这样的视觉装饰,用户不能直接对其进行关闭或其它操作。所有对窗口的操作都需要通过其编程接口来实现。应用程序可以借助窗口对象来进行事件传递。窗口对象会持续跟踪当前的第一响应者对象,并在UIApplication对象提出请求时将事件传递它。

还有一件可能让有经验的Mac OS X开发者觉得奇怪的事是UIWindow类的继承关系。在MacOS X中,NSWindow的父类是NSResponder;而在iPhoneOS中,UIWindow的父类是UIView。因此,窗口在iPhoneOS中也是一个视图对象。不管其起源如何,您通常可以将iPhone OS上的窗口和Mac OSX的窗口同样对待。也就是说,您通常不必直接操作UIWindow对象中与视图有关的属性变量

在创建应用程序窗口时,您应该总是将其初始的边框尺寸设置为整个屏幕的大小。如果您的窗口是从nib文件装载得到,InterfaceBuilder并不允许创建比屏幕尺寸小的窗口;然而,如果您的窗口是通过编程方式创建的,则必须在创建时传入期望的边框矩形。除了屏幕矩形之外,没有理由传入其它边框矩形。屏幕矩形可以通过UIScreen对象来取得,具体代码如下所示:

UIWindow* aWindow = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];

虽然iPhoneOS支持将一个窗口叠放在其它窗口的上方,但是您的应用程序永远不应创建多个窗口。系统自身使用额外的窗口来显示系统状态条、重要的警告、以及位于应用程序窗口上方的其它消息。如果您希望在自己的内容上方显示警告,可以使用UIKit提供的警告视图,而不应创建额外的窗口。

UIView是作用

视图UIView类的实例,负责在屏幕上定义一个矩形区域。在iPhone的应用程序中,视图在展示用户界面及响应用户界面交互方面发挥关键作用。每个视图对象都要负责渲染视图矩形区域中的内容,并响应该区域中发生的触碰事件。这一双重行为意味着视图是应用程序与用户交互的重要机制。在一个基于模型-视图-控制器的应用程序中,视图对象明显属于视图部分。

除了显示内容和处理事件之外,视图还可以用于管理一或多个子视图。子视图是指嵌入到另一视图对象边框内部的视图对象,而被嵌入的视图则被称为父视图或超视图。视图的这种布局方式被称为视图层次,一个视图可以包含任意数量的子视图,通过为子视图添加子视图的方式,视图可以实现任意深度的嵌套。视图在视图层次中的组织方式决定了在屏幕上显示的内容,原因是子视图总是被显示在其父视图的上方;这个组织方法还决定了视图如何响应事件和变化。每个父视图都负责管理其直接的子视图,即根据需要调整它们的位置和尺寸,以及响应它们没有处理的事件。

由于视图对象是应用程序和用户交互的主要途径,所以需要在很多方面发挥作用,下面是其中的一小部分:

  • 描画和动画

    • 视图负责对其所属的矩形区域进行描画。

    • 某些视图属性变量可以以动画的形式过渡到新的值。

  • 布局和子视图管理

    • 视图管理着一个子视图列表。

    • 视图定义了自身相对于其父视图的尺寸调整行为。

    • 必要时,视图可以通过代码调整其子视图的尺寸和位置。

    • 视图可以将其坐标系统下的点转换为其它视图或窗口坐标系统下的点。

  • 事件处理

    • 视图可以接收触摸事件。

    • 视图是响应者链的参与者。

在iPhone应用程序中,视图和视图控制器紧密协作,管理若干方面的视图行为。视图控制器的作用是处理视图的装载与卸载、处理由于设备旋转导致的界面旋转,以及和用于构建复杂用户界面的高级导航对象进行交互。更多这方面的信息请参见“视图控制器的作用”部分。

本章的大部分内容都着眼于解释视图的这些作用,以及说明如何将您自己的定制代码关联到现有的UIView行为中。

UIKit的视图类

UIView类定义了视图的基本行为,但并不定义其视觉表示。相反,UIKit通过其子类来为像文本框、按键、及工具条这样的标准界面元素定义具体的外观和行为。图2-1显示了所有UIKit视图类的层次框图。除了UIViewUIControl类是例外,这个框图中的大多数视图都设计为可直接使用,或者和委托对象结合使用。

图2-1  视图的类层次

View class hierarchy

这个视图层次可以分为如下几个大类:

  • 容器

    容器视图用于增强其它视图的功能,或者为视图内容提供额外的视觉分隔。比如,UIScrollView类可以用于显示因内容太大而无法显示在一个屏幕上的视图。UITableView类是UIScrollView类的子类,用于管理数据列表。表格的行可以支持选择,所以通常也用于层次数据的导航—比如用于挖掘一组有层次结构的对象。

    UIToolbar对象则是一个特殊类型的容器,用于为一或多个类似于按键的项提供视觉分组。工具条通常出现在屏幕的底部。Safari、Mail、和Photos程序都使用工具条来显示一些按键,这些按键代表经常使用的命令。工具条可以一直显示,也可以根据应用程序的需要进行显示。

  • 控件

    控件用于创建大多数应用程序的用户界面。控件是一种特殊类型的视图,继承自UIControl超类,通常用于显示一个具体的值,并处理修改这个值所需要的所有用户交互。控件通常使用标准的系统范式(比如目标-动作模式和委托模式)来通知应用程序发生了用户交互。控件包括按键、文本框、滑块、和切换开关。

  • 显示视图

    控件和很多其它类型的视图都提供了交互行为,而另外一些视图则只是用于简单地显示信息。具有这种行为的UIKit类包括UIImageView、 UILabelUIProgressViewUIActivityIndicatorView

  • 文本和web视图

    文本和web视图为应用程序提供更为高级的显示多行文本的方法。UITextView类支持在滚动区域内显示和编辑多行文本;而UIWebView类则提供了显示HTML内容的方法,通过这个类,您可以将图形和高级的文本格式选项集成到应用程序中,并以定制的方式对内容进行布局。

  • 警告视图和动作表单

    警告视图和动作表单用于即刻取得用户的注意。它们向用户显示一条消息,同时还有一或多个可选的按键,用户通过这些按键来响应消息。警告视图和动作表单的功能类似,但是外观和行为不同。举例来说,UIAlertView类在屏幕上弹出一个蓝色的警告框,而UIActionSheet类则从屏幕的底部滑出动作框。

  • 导航视图

    页签条和导航条和视图控制器结合使用,为用户提供从一个屏幕到另一个屏幕的导航工具。在使用时,您通常不必直接创建UITabBarUINavigationBar的项,而是通过恰当的控制器接口或InterfaceBuilder来对其进行配置。

  • 窗口

    窗口提供一个描画内容的表面,是所有其它视图的根容器。每个应用程序通常都只有一个窗口。更多信息请参见“UIWindow的作用”部分。

除了视图之外,UIKit还提供了视图控制器,用于管理这些对象。更多信息请参见“视图控制器的作用”部分。

视图控制器的作用

运行在iPhoneOS上的应用程序在如何组织内容和如何将内容呈现给用户方面有很多选择。含有很多内容的应用程序可以将内容分为多个屏幕。在运行时,每个屏幕的背后都是一组视图对象,负责显示该屏幕的数据。一个屏幕的视图后面是一个视图控制器其作用是管理那些视图上显示的数据,并协调它们和应用程序其它部分的关系。

UIViewController类负责创建其管理的视图及在低内存时将它们从内容中移出。视图控制器还为某些标准的系统行为提供自动响应。比如,在响应设备方向变化时,如果应用程序支持该方向,视图控制器可以对其管理的视图进行尺寸调整,使其适应新的方向。您也可以通过视图控制器来将新的视图以模式框的方式显示在当前视图的上方。

除了基础的UIViewController类之外,UIKit还包含很多高级子类,用于处理平台共有的某些高级接口。特别需要提到的是,导航控制器用于显示多屏具有一定层次结构的内容;而页签条控制器则支持用户在一组不同的屏幕之间切换,每个屏幕都代表应用程序的一种不同的操作模式。

有关如何通过视图控制器管理用户界面上视图的更多信息,请参见iPhoneOS的视图控制器编程指南

视图架构和几何属性

由于视图是iPhone应用程序的焦点对象,所以对视图与系统其它部分的交互机制有所了解是很重要的。UIKit中的标准视图类为应用程序免费提供相当数量的行为,还提供了一些定义良好的集成点,您可以通过这些集成点来对标准行为进行定制,完成应用程序需要做的工作。

本文的下面部分将解释视图的标准行为,并说明哪些地方可以集成您的定制代码。如果需要特定类的集成点信息,请参见该类的参考文档。您可以从UIKit框架参考中取得所有类参考文档的列表。

视图交互模型

任何时候,当用户和您的程序界面进行交互、或者您的代码以编程的方式进行某些修改时,UIKit内部都会发生一个复杂的事件序列。在事件序列的一些特定的点上,UIKit会调用您的视图类,使它们有机会代表应用程序进行事件响应。理解这些调用点是很重要的,有助于理解您的视图对象和系统在哪里进行结合。图2-2显示了从用户触击屏幕到图形系统更新屏幕内容这一过程的基本事件序列。以编程方式触发事件的基本步骤与此相同,只是没有最初的用户交互。

图2-2  UIKit和您的视图对象之间的交互

UIKit interactions with your view objects

下面的步骤说明进一步刨析了图2-2中的事件序列,解释了序列的每个阶段都发生了什么,以及应用程序可能如何进行响应。

  1. 用户触击屏幕。

  2. 硬件将触击事件报告给UIKit框架。

  3. UIKit框架将触击信息封装为一个UIEvent对象,并派发给恰当的视图(有关UIKit如何将事件递送给您的视图的详细解释,请参见“事件的传递”部分)。

  4. 视图的事件处理方法可以通过下面的方式来响应事件:

    • 调整视图或其子视图的属性变量(边框、边界、透明度等)。

    • 将视图(或其子视图)标识为需要修改布局。

    • 将视图(或其子视图)标识为布局需要重画。

    • 将数据发生的变化通报给控制器

    当然,上述的哪些事情需要做及调用什么方法来完成是由视图来决定的。

  5. 如果视图被标识为需要重新布局,UIKit就调用视图的layoutSubviews方法。

    您可以在自己的定制视图中重载这个方法,以便调整子视图的尺寸和位置。举例来说,如果一个视图具有很大的滚动区域,就需要使用几个子视图来“平铺”,而不是创建一个内存很可能装不下的大视图。在这个方法的实现中,视图可以隐藏所有不需显示在屏幕上的子视图,或者在重新定位之后将它们用于显示新的内容。作为这个过程的一部分,视图也可以将用于“平铺”的子视图标识为需要重画。

  6. 如果视图的任何部分被标识为需要重画,UIKit就调用该视图的drawRect:方法。

    UIKit只对那些需要重画的视图调用这个方法。在这个方法的实现中,所有视图都应该尽可能快地重画指定的区域,且都应该只重画自己的内容,不应该描画子视图的内容。在这个调用点上,视图不应该尝试进一步改变其属性或布局。

  7. 所有更新过的视图都和其它可视内容进行合成,然后发送给图形硬件进行显示。

  8. 图形硬件将渲染完成的内容转移到屏幕。

请注意:上述的更新模型主要适用于采纳内置视图和描画技术的应用程序。如果您的应用程序使用OpenGLES来描画内容,则通常要配置一个全屏的视图,然后直接在OpenGL的图形上下文中进行描画。您的视图仍然需要处理触碰事件,但不需要对子视图进行布局或者实现drawRect:方法。有关OpenGLES的更多信息,请参见“用OpenGLES进行描画”部分。

基于上述的步骤说明可以看出,UIKit为您自己定制的视图提供如下主要的结合点:

  1. 下面这些事件处理方法:

    • touchesBegan:withEvent:

    • touchesMoved:withEvent:

    • touchesEnded:withEvent:

    • touchesCancelled:withEvent:

  2. layoutSubviews方法

  3. drawRect:方法

大多数定制视图通过实现这些方法来得到自己期望的行为。您可能不需要重载所有方法,举例来说,如果您实现的视图是固定尺寸的,则可能不需要重载layoutSubviews方法。类似地,如果您实现的视图只是显示简单的内容,比如文本或图像,则通常可以通过简单地嵌入UIImageViewUILabel对象作为子视图来避免描画。

重要的是要记住,这些是主要的结合点,但不是全部。UIView类中有几个方法的设计目的就是让子类重载的。您可以通过查阅UIView类参考中的描述来了解哪些方法可以被重载。

视图渲染架构

虽然您通过视图来表示屏幕上的内容,但是UIView类自身的很多基础行为却严重依赖于另一个对象。UIKit中每个视图对象的背后都有一个CoreAnimation层对象,它是一个CALayer类的实例,该类为视图内容的布局和渲染、以及合成和动画提供基础性的支持。

和Mac OS X(在这个平台上Core Animation支持是可选的)不同的是,iPhone OS将CoreAnimation集成到视图渲染实现的核心。虽然Core Animation发挥核心作用,但是UIKit在CoreAnimation上面提供一个透明的接口层,使编程体验更为流畅。这个透明的接口使开发者在大多数情况下不必直接访问CoreAnimation的层,而是通过UIView的方法和属性声明取得类似的行为。然而,当UIView类没有提供您需要的接口时,CoreAnimation就变得重要了,在那种情况下,您可以深入到Core Animation层,在应用程序中实现一些复杂的渲染。

本文的下面部分将介绍Core Animation技术,描述它通过UIView类为您提供的一些功能。有关如何使用CoreAnimation进行高级渲染的更多信息,请参见CoreAnimation编程指南

Core Animation基础

Core Animation利用了硬件加速和架构上的优化来实现快速渲染和实时动画。当视图的drawRect:方法首次被调用时,层会将描画的结果捕捉到一个位图中,并在随后的重画中尽可能使用这个缓存的位图,以避免调用开销很大的drawRect:方法。这个过程使CoreAnimation得以优化合成操作,取得期望的性能。

CoreAnimation把和视图对象相关联的层存储在一个被称为层树的层次结构中。和视图一样,层树中的每个层都只有一个父亲,但可以嵌入任意数量的子层。缺省情况下,层树中对象的组织方式和视图在视图层次中的组织方式完全一样。但是,您可以在层树中添加层,而不同时添加相应的视图。当您希望实现某种特殊的视觉效果、而又不需要在视图上保持这种效果时,就可能需要这种技术。

实际上,层对象是iPhoneOS渲染和布局系统的推动力,大多数视图属性实际上是其层对象属性的一个很薄的封装。当您(直接使用CALayer对象)修改层树上层对象的属性时,您所做的改变会立即反映在层对象上。但是,如果该变化触发了相应的动画,则可能不会立即反映在屏幕上,而是必须随着时间的变化以动画的形式表现在屏幕上。为了管理这种类型的动画,CoreAnimation额外维护两组层对象,我们称之为表示树渲染树

表示树反映的是层在展示给用户时的当前状态。假定您对层值的变化实行动画,则在动画开始时,表示层反映的是老的值;随着动画的进行,CoreAnimation会根据动画的当前帧来更新表示树层的值;然后,渲染树就和表示树一起,将变化渲染在屏幕上。由于渲染树运行在单独的进程或线程上,所以它所做的工作并不影响应用程序的主运行循环。虽然层树和表示树都是公开的,但是渲染树的接口是私有。

在视图后面设置层对象对描画代码的性能有很多重要的影响。使用层的好处在于视图的大多数几何变化都不需要重画。举例来说,改变视图的位置和尺寸并需要重画视图的内容,只需简单地重用层缓存的位图就可以了。对缓存的内容实行动画比每次都重画内容要有效得多。

使用层的缺点在于层是额外的缓存数据,会增加应用程序的内存压力。如果您的应用程序创建太多的视图,或者创建多个很大的视图,则可能很快就会出现内存不够用的情形。您不用担心在应用程序中使用视图,但是,如果有现成的视图可以重用,就不要创建新的视图对象。换句话说,您应该设法使内存中同时存在的视图对象数量最小。

有关Core Animation的进一步概述、对象树、以及如何创建动画,请参见CoreAnimation编程指南

改变视图的层

在iPhone OS系统中,由于视图必须有一个与之关联的层对象,所以UIView类在初始化时会自动创建相应的层。您可以通过视图的layer属性访问这个层,但是不能在视图创建完成后改变层对象。

如果您希望视图使用不同类型的层,必须重载其layerClass类方法,并在该方法中返回您希望使用的层对象。使用不同层类的最常见理由是为了实现一个基于OpenGL的应用程序。为了使用OpenGL描画命令,视图下面的层必须是CAEAGLLayer类的实例,这种类型的层可以和OpenGL渲染调用进行交互,最终在屏幕上显示期望的内容。

重要提示:您永远不应修改视图层的delegate属性,该属性用于存储一个指向视图的指针,应该被认为是私有的。类似地,由于一个视图只能作为一个层的委托,所以您必须避免将它作为其它层对象的委托,否则会导致应用程序崩溃。

 

动画支持

iPhoneOS的每个视图后面都有一个层对象,这样做的好处之一是使视图内容更加易于实现动画。请记住,动画并不一定是为了在视觉上吸引眼球,它可以将应用程序界面变化的上下文呈现给用户。举例来说,当您在屏幕转移过程中使用过渡时,过渡本身就向用户指示屏幕之间的联系。系统自动支持了很多经常使用的动画,但您也可以为界面上的其它部分创建动画。

UIView类的很多属性都被设计为可动画的(animatable)。可动画的属性是指当属性从一个值变为另一个值的时候,可以半自动地支持动画。您仍然必须告诉UIKit希望执行什么类型的动画,但是动画一旦开始,CoreAnimation就会全权负责。UIView对象中支持动画的属性有如下几个:

  • frame

  • bounds

  • center

  • transform

  • alpha

虽然其它的视图属性不直接支持动画,但是您可以为其中的一部分显式创建动画。显式动画要求您做很多管理动画和渲染内容的工作,通过使用CoreAnimation提供的基础设施,这些工作仍然可以得到良好的性能。

有关如何通过UIView类创建动画的更多信息,请参见“实现视图动画”部分;有关如何创建显式动画的更多信息,则请参见CoreAnimation编程指南

视图坐标系统

UIKit中的坐标是基于这样的坐标系统:以左上角为坐标的原点,原点向下和向右为坐标轴正向。坐标值由浮点数来表示,内容的布局和定位因此具有更高的精度,还可以支持与分辨率无关的特性。图2-3显示了这个相对于屏幕的坐标系统,这个坐标系统同时也用于UIWindowUIView类。视图坐标系统的方向和Quartz及MacOS X使用的缺省方向不同,选择这个特殊的方向是为了使布局用户界面上的控件及内容更加容易。

图2-3  视图坐标系统

View coordinate system

您在编写界面代码时,需要知道当前起作用的坐标系统。每个窗口和视图对象都维护一个自己本地的坐标系统。视图中发生的所有描画都是相对于视图本地的坐标系统。但是,每个视图的边框矩形都是通过其父视图的坐标系统来指定,而事件对象携带的坐标信息则是相对于应用程序窗口的坐标系统。为了方便,UIWindowUIView类都提供了一些方法,用于在不同对象之间进行坐标系统的转换。

虽然Quartz使用的坐标系统不以左上角为原点,但是对于很多Quartz调用来说,这并不是问题。在调用视图的drawRect:方法之前,UIKit会自动对描画环境进行配置,使左上角成为坐标系统的原点,在这个环境中发生的Quartz调用都可以正确地在视图中描画。您唯一需要考虑不同坐标系统之间差别的场合是当您自行通过Quartz建立描画环境的时候。

更多有关坐标系统、Quartz、和描画的一般信息,请参见“图形和描画”部分。

边框、边界、和中心的关系

视图对象通过framebounds、和center属性声明来跟踪自己的大小和位置。frame属性包含一个矩形,即边框矩形,用于指定视图相对于其父视图坐标系统的位置和大小。bounds属性也包含一个矩形,即边界矩形,负责定义视图相对于本地坐标系统的位置和大小。虽然边界矩形的原点通常被设置为(0, 0),但这并不是必须的。center属性包含边框矩形的中心点

在代码中,您可以将framebounds、和center属性用于不同的目的。边界矩形代表视图本地的坐标系统,因此,在描画和事件处理代码中,经常借助它来取得视图中发生事件或需要更新的位置。中心点代表视图的中心,改变中心点一直是移动视图位置的最好方法。边框矩形是一个通过boundscenter属性计算得到的便利值,只有当视图的变换属性被设置恒等变换时,边框矩形才是有效的。

图2-4显 示了边框矩形和边界矩形之间的关系。右边的整个图像是从视图的(0,0)开始描画的,但是由于边界的大小和整个图像的尺寸不相匹配,所以位于边界矩形之外的图像部分被自动裁剪。在视图和它的父视图进行合成的时候,视图在其父视图中的位置是由视图边框矩形的原点决定的。在这个例子中,该原点是(5,5)。结果,视图的内容就相对于父视图的原点向下向右移动相应的尺寸。

图2-4  视图的边框和边界之间的关系

Relationship between a view's frame and bounds

如果没有经过变换,视图的位置和大小就由上述三个互相关联的属性决定的。当您在代码中通过initWithFrame:方法创建一个视图对象时,其frame属性就会被设置。该方法同时也将bounds矩形的原点初始化为(0.0,0.0),大小则和视图的边框相同。然后center属性会被设置为边框的中心点。

虽然您可以分别设置这些属性的值,但是设置其中的一个属性会引起其它属性的改变,具体关系如下:

  • 当您设置frame属性时,bounds属性的大小会被设置为与frame属性的大小相匹配的值,center属性也会被调整为与新的边框中心点相匹配的值。

  • 当您设置center属性时,frame的原点也会随之改变。

  • 当您设置bounds矩形的大小时,frame矩形的大小也会随之改变。

您可以改变bounds的原点而不影响其它两个属性。当您这样做时,视图会显示您标识的图形部分。在图2-4中,边界的原点被设置为(0.0,0.0)。在图2-5中,该原点被移动到(8.0,24.0)。结果,显示出来的是视图图像的不同部分。但是,由于边框矩形并没有改变,新的内容在父视图中的位置和之前是一样的。

图2-5  改变视图的边界

Altering a view's bounds

请注意:缺省情况下,视图的边框并不会被父视图的边框裁剪。如果您希望让一个视图裁剪其子视图,需要将其clipsToBounds属性设置为YES

坐标系统变换

在视图的drawRect:方法中常常借助坐标系统变换来进行描画。而在iPhoneOS系统中,您还可以用它来实现视图的某些视觉效果。举例来说,UIView类中包含一个transform属性声明,您可以通过它来对整个视图实行各种类型的平移、比例缩放、和变焦缩放效果。缺省情况下,这个属性的值是一个恒等变换,不会改变视图的外观。在加入变换之前,首先要得到该属性中存储的CGAffineTransform结构,用相应的CoreGraphics函数实行变换,然后再将修改后的变换结构重新赋值给视图的transform属性。

请注意:当您将变换应用到视图时,所有执行的变换都是相对于视图的中心点。

平移一个视图会使其所有的子视图和视图本身的内容一起移动。由于子视图的坐标系统是继承并建立在这些变化的基础上的,所以比例缩放也会影响子视图的描画。有关如何控制视图内容缩放的更多信息,请参见“内容模式和比例缩放”部分。

重要提示:如果transform属性的值不是恒等变换,则frame属性的值就是未定义的,必须被忽略。在设置变换属性之后,请使用boundscenter属性来获取视图的位置和大小。

 

有关如何在drawRect:方法中使用变换的信息,请参见“坐标和坐标变换”部分;有关用于修改CGAffineTransform结构的函数,则请参见CGAffineTransform参考


原创粉丝点击