技术开发 频道

iPhone 开发基础教程之三

  【IT168 技术文档】

        构建QuartzFun应用程序

  在Xcode中,使用基于视图的应用程序模板创建一个新项目,并将其命名为QuartzFun。创建项目之后,展开Classes和Resources文件夹,单击Classes文件夹,以便我们可以添加类。这个模板已经为我们提供了一个应用程序委托和一个视图控制器。我们将在视图中执行自定义绘图,因此需要创建一个UIView子类。在该子类中,我们将通过覆盖drawRect:方法进行绘图。创建一个新的 Cocoa Touch Classes文件,并选择UIView subclass模板。将该文件命名为QuartzFunView.m,并确保创建了头文件。

  与之前一样,我们将定义一些常量,但这次定义的常量是多个类所需要的,并且不是特定于某个类的。我们将只为常量创建头文件,因此通过以下访问创建一个新文件:从Other栏中选择Empty File模板,并将其命名为Constants.h。

  一些Quartz 2D示例,来自于苹果公司提供的Quartz Demo示例项目

  我们还需要创建两个文件。图中,你可以看到我们提供了一个选择随机颜色的选项。但UIColor没有提供返回随机颜色的方法,因此我们必须编写代码来执行该操作。当然,我们将该代码放置到控制器类中,但是由于我们是了解Objective-C的程序员,因此将该代码放置到UIColor上的某个类别中。使用Empty File模板创建两个文件,将它们分别命名为UIColor-Random.h和UIColor-Random.m。或者,使用NSObject subclass模板创建UIColor-Random.m,并让该模板为你自动创建UIColor-Random.h;然后删除这两个文件的内容。

  创建随机颜色

  让我们首先处理此类别。在UIColor-Random.h中,添加以下代码:

  现在,切换到UIColor-Random.m并添加以下内容:

 

  这非常简单。我们声明一个静态变量,该变量告诉我们该方法是否是第一次调用。应用程序运行期间第一次调用该方法时,我们将运行随机数字生成器。在此处执行此操作意味着:我们不必依赖应用程序在其他地方执行该操作,因此,我们可以在其他iPhone项目中重用此类别。

  在运行随机数字生成器之后,生成三个随机的CGFloat,其值介于0.0和1.0之间,使用这3个值来创建新的颜色。我们将alpha设置为1.0,以便所有生成的颜色都是不透明的。

   定义应用程序常量

  我们将使用分段控制器为用户可以选择的每个选项定义常量。单击Constants.h并添加以下内容:

 

  为了使代码更具有可读性,我们使用typedef声明了两个枚举类型。

  实现QuartzFunView框架

  由于我们将在UIView的某个子类中进行绘图,因此让我们用其所需的所有内容设置该类,但进行绘图的实际代码除外,我们稍后将添加这些代码。单击QuartzFunView.h并进行以下更改:

 

  我们做的第一件事情就是导入刚才创建的Constants.h头文件,这样便可以使用枚举。然后,声明实例变量。前两个变量将跟踪用户拖过屏幕的手指。我们将用户第一次触摸屏幕的位置存储在firstTouch中,将拖动时手指的位置以及拖动结束时手指的位置存储在lastTouch中。我们的绘图代码将使用这两个变量来确定在哪里绘制请求的形状。

  接下来,我们定义某种颜色来存放用户的颜色选择,并定义一个ShapeType以跟踪用户想绘制的形状。然后,定义一个UIImage属性,该属性存放用户选择底部工具栏中最右侧项目时在屏幕上绘制的图像(参见图)。我们定义的最后一个属性是 Boolean,它用于跟踪用户是否请求随机颜色。

  切换到QuartzFunView.m并进行以下更改:

 
 
 

  由于该视图是从nib中加载的,因此我们首先实现initWithCoder:。请记住nib中的对象实例将存储为归档对象,这与我们在上一章将对象归档和加载到磁盘所使用的机制完全相同。因此,从nib中加载对象实例时,init:或initWith- Frame:都不会调用。而是使用initWithCoder:,因此任何初始化代码都需要在这里添加。若要将初始颜色值设置为红色,则将 useRandomColor初始化为NO,并加载我们将要绘制的图像文件。你不必完全理解此处的其他代码。我们将在第13章详细讨论使用 touchesBegan:withEvent:、touchesMoved:withEvent:和touchesEnded:withEvent:方法的技巧和具体细节。简单地说,可以覆盖这3个UIView方法以确定用户触摸iPhone屏幕的位置。

  当用户手指第一次触摸屏幕时会调用touchesBegan:withEvent:。在该方法中,如果用户已经使用之前添加到UIColor中的新 randomColor方法选择了某个随机颜色,则我们需要更改此颜色。之后,我们存储当前位置,这样便可以知道用户第一次触摸屏幕的位置,并指出:需要通过在self上调用setNeedsDisplay来重新绘制视图。

  当用户在屏幕上拖动手指时会连续调用接下来的touchesMoved:withEvent:方法。此处,我们所要做的就是将新位置存储在lastTouch中,并指出需要重新绘制该屏幕。

  当用户将手指从屏幕上抬起时会调用最后一个方法,即touchesEnded:withEvent:。就像在touchesMoved:withEvent:方法中一样,我们所要做的就是将最后一个位置存储在lastTouch变量中,并指出需要重新绘制该视图。

  如果你还没有全面了解这3个方法在触摸过程中所执行的操作,请不用担心,我们将在后续章节中更详细地介绍这些方法。

  完成应用程序骨架并运行之后,我们将重新回顾这些方法。drawRect:方法就是此应用程序的主体部分,目前仅包含一条注释,因为我们尚未编写该方法。我们首先需要完成应用程序设置,然后再添加绘图代码。

  向视图控制器中添加输出口和操作

  如果参考图12?1,你会看到我们的界面包括两个分段控制器,一个位于屏幕顶部,另一个位于屏幕底部。使用顶部的控制器,用户可以选择颜色,但该分段控制器仅应用于底部4个选项中的3个选项,因此我们需要一个到顶部分段控制器的输出口,以便当它不起作用时将它隐藏。我们还需要两个方法,一个方法是在选择新颜色时调用,另一个方法是在选择新形状时调用。

  单击QuartzFunViewController.h并进行以下更改:

 

  所做的更改一目了然。接下来,切换到QuartzFunViewController.m并进行以下更改:

 

  这段代码也非常简单。在changeColor:方法中,我们确定已选择的分段,并根据选择内容创建一个新颜色。我们将view转换为 QuartzFunView。接下来,设置它的currentColor属性,以便它知道绘图所使用的颜色。但选择随机颜色时除外,如果选择随机颜色,我们只需将视图的useRandomColor属性设置为YES。由于所有绘图代码将包含在视图内部,因此我们不必对其他方法执行任何操作。

  在changeShape:方法中,我们的做法类似。但是,由于不必创建对象,因此我们只需将视图的 shapeType属性设置为来自sender的分段索引。还记得ShapeType enum吗?enum的4个元素与应用程序视图底部的4个工具栏分段相对应。我们将形状设置为与当前所选择的分段相同,并根据是否选择了Image分段来隐藏colorControl和取消隐藏colorControl。

  更新QuartzFunViewController.xib

开始绘图之前,我们需要向nib中添加分段控件,然后连接操作和输出口。双击QuartzFunView- Controller.xib,在Interface Builder中将其打开。第一件事是更改视图的类,因此在标签为QuartzFunViewController.xib的窗口中单击View图标,并按打开身份检查器。将该类从UIView改为QuartzFunView。

  在库中找到Navigation Bar。确保你控制的是Navigation Bar,而非Navigation Controller。我们只是希望该工具栏位于视图顶部。将Navigation Bar紧贴在视图窗口的顶部。

  接下来,在库中找到Segmented Control,并将该控件拖动到Navigation Bar的顶部。放下该控件之后,它应该仍然为选中状态。捕捉此分段控件任何一侧的调整大小的点,调整它的大小,以便它占据导航栏的整个宽度。你不会看到任何蓝色引导线,但这种情况下,Interface Builder会限制该栏的最大大小,因此只需拖动它直到不再进一步展开为止。

  按调出属性检查器,并将分段数量从2更改为5。依次双击各分段,将它们的标签分别改为(从左到右)Red、Blue、Yellow、Green和Random。此时,你的View窗口应该类似于图12?7。

  按住Control的同时,将File’s Owner图标拖到分段控件上,并选择colorControl输出口。接下来,确保选中分段控件,并按z2调出连接检查器。从Value Changed事件拖到File’s Owner并选择changeColor:操作。

  现在,在库中找到Toolbar,从中拖出一个工具栏并将其放置在窗口底部。库中的Toolbar上有一个我们并不需要的按钮,因此选择该按钮并按下Delete键。放置控件并删除按钮之后,选中另一个Segmented Control,并将其拖到工具栏上。

  结果是,分段控件在工具栏中居中有点困难,因此我们将提供一点帮助。将Flexible Space Bar Button Item从库中拖到位于分段控件左侧的工具栏上。接下来,将另一个Flexible Space Bar Button Item拖到位于分段控件右侧的工具栏上。当我们调整该工具栏的大小时,这些项目将使分段控件位于工具栏的中心。单击分段控件以将其选中,并调整其大小以使它适合此工具栏,其中左右两侧各留有一点空间。

  接下来,按打开属性检查器,并将分段数从2更改为4。按顺序将4个分段的标题改为Line、Rect、Ellipse和Image。切换到连接检查器,并将Value Changed事件连接到File’s Owner的changeShape:操作方法。保存并关闭nib,然后返回Xcode。

  说明 你可能想知道为什么我们将一个导航栏放置在视图的顶部,将一个工具栏放置在视图的底部。根据苹果公司发布的“iPhone人机界面指南”,导航栏是经过专门设计的,它在屏幕顶部看起来比较美观,而工具栏则是专门为底部而设计。如果你在Interface Builder的库窗口中阅读了Toolbar和Navigation Bar的描述,你将会看到对此设计意图的说明。

  编译并运行应用程序,确保一切正常。目前你还不能在屏幕上绘制形状,但分段控件可以工作,当在底部控件中轻击Image分段时,颜色控件会消失。一切都开始工作之后,我们进行绘图。

  绘制直线

  返回Xcode,编辑QuartzFunView.m,并用以下代码替换空的drawRect:方法:

  首先,检索对当前上下文的引用,以便知道要绘图的位置:

 

  接下来,将线宽设置为2.0,这意味着我们画的任何直线都是2个像素宽:

 

  随后,设置所画直线的颜色。由于UIColor有该方法所需的CGColor属性,因此我们使用currentColor实例变量的这个属性将正确的颜色传递给该函数:

 

  我们使用switch跳转到每个形状类型的相应代码。我们将从处理kLineShape的代码开始,使其正常工作,然后依次为每个形状添加代码:

 
  要绘制直线,我们将不可见画笔移动到用户触摸的第一个位置。请记住,我们将该值存储在touchesBegan:方法中,以便它总是反映用户上次触摸或拖动时触摸的第一个点。

  接下来,我们绘制一条从该点到用户触摸的最后一个点的直线。如果用户的手指仍然与屏幕接摸,则lastTouch包含用户手指的当前位置。如果用户的手离开了屏幕,则lastTouch包含用户手指离开屏幕时的位置。

  然后,画出这条路径。以下函数将画出一条直线,这是我们使用之前设置的颜色和宽度绘制成的:

  随后,我们只需完成此switch语句就可以了,如下所示:

  

  在构建和运行之前,还有最后一个步骤。由于Quartz 2D是Core Graphics的一部分,因此我们需要将Core Graphics框架添加到项目中。在项目窗口中,单击Groups & Files窗格中的Frameworks,并添加此框架。如果你忘记了具体细节,请参考第5章。你将此书页折起角了,对吗?

  此时,你应该能够进行编译和运行了。Rect、Ellipse和Shape选项将不可用,但你应该能够很好地绘制直线(参见图12?8)。

  绘制矩形和椭圆形

  让我们实现同时绘制矩形和椭圆形的代码,因为Quartz 2D基本上采用相同的方法实现这两个对象。对drawRect:方法进行以下更改:

  由于我们希望将椭圆形和矩形涂上纯色,因此我们添加一个使用currentColor设置填充颜色的调用:

  接下来,我们声明一个CGRect变量。我们将使用currentRect来存放由用户拖动描述的矩形。请记住,CGRect包含两个成员:size和 origin。通过CGRectMake()函数,我们可以通过指定x、y、width和height值来创建 CGRect,因此可用于绘制矩形。绘制矩形的代码乍看上去有点吓人,但实际上并没有那么复杂。用户可以向任何方向拖动,因此起点因拖动方向而异。我们使用两个点中较小的x值和两个点中较小的y值来创建起点。然后通过获得两个x值和两个y值之差的绝对值来计算出大小:

  定义此矩形之后,绘制矩形或椭圆形就像调用两个函数一样轻松,一个函数是绘制矩形或在我们定义的CGRect中绘制椭圆形,另一个函数是绘画并填充它。

 
  编译并运行应用程序,并试用Rect和Ellipse工具,看看你有多喜欢它们。不要忘记不时更改颜色和试用随机颜色。

     绘制图像

  我们的最后一件事是绘制图像。12 QuartzFun文件夹中包含一个名为iphone.png的图像,你可以将该图像添加到Resources文件夹中,或者也可以添加你要使用的任何.png文件,只要记得将代码中的文件名改为所选图像即可。

  向drawRect:方法中添加以下代码:

  注意,在switch语句中,我们在case kImageShape:下的代码两侧添加了花括号。GCC在case语句之后的第一行中声明变量时遇到了问题。这些花括号是我们告诉GCC停止抱怨的一种方式。我们还在switch语句之前声明了horizontalOffset,该方法将相关代码放到了一起。

  首先,我们计算该图像的中心,因为我们希望绘制的图像以用户上次触摸的点为中心。如果不进行调整,则会在用户手指的左上角绘制该图像,这也是一个有效的选项。然后通过从lastTouch中的x和y值中减去这些偏移量来生成一个新的Cgpoint。

 
  现在,我们通知图像绘制自身。此行代码将进行这项工作:
  应用程序如预期执行,但我们应该考虑进行一些优化。在该应用程序中,你不会注意到速度减慢,但是在更复杂的应用程序(在速度较慢的处理器上运行)中,你会看到某些延迟。该问题由touchesMoved:和touchesEnded:方法中的 QuartzFunView.m引起。这两个方法都包含下面这行代码:
       [Self SedNeedsDisplay];

  很明显,这是我们告知视图重新绘制自身的方式。该代码正常工作,但它导致整个视图被擦除并重新绘制,即使只是非常微小的更改也是如此。当我们准备拖动新形状时,我们希望擦除该屏幕,但我们不希望在拖动形状时一秒钟清除屏幕好几次。

  为避免在拖动期间多次强制重新绘制整个视图,我们可以使用setNeedsDisplayInRect:。 setNeedsDisplayInRect:是一个NSView方法,该方法会将视图区域的一个矩形部分标记为需要重新显示。我们需要重新绘制的不仅仅是firstTouch和lastTouch之间的矩形,还有当前拖动所包围的任何屏幕部分。如果用户触摸屏幕,然后在屏幕上到处乱画,则只需重新绘制 firstTouch和lastTouch之间的部分,将许多不需要的已绘制的内容留在屏幕上。

  答案是跟踪受CGRect实例变量中的特定拖动影响的整个区域。在touchesBegan:中,我们将该实例变量重置为仅用户触摸的点。然后在 touchesMoved:和touchesEnded:中,使用一个Core Graphics函数获取当前矩形和存储的矩形的并集,然后存储所得到的矩形。此外,还使用该函数指定需要重新绘制的视图部分。该方法为我们提供了受当前拖动影响的正在运行的全部区域。

0
相关文章