View
由于视图对象是您的应用程序与用户交互的主要方式,因此它们承担着许多责任。这里仅仅是少数:
布局和子视图管理
视图相对于其父视图定义了自己的默认大小调整行为。
视图可以管理很多子视图。
视图可以根据需要覆盖其子视图的大小和位置。
视图可以将其坐标系中的点转换为其他视图或窗口的坐标系。
绘画和动画
视图在其矩形区域中绘制内容。
某些视图属性的值可以增加动画。
事件处理
视图可以接收触摸事件。
视图参与响应者链。
本章重点介绍创建,管理和绘制视图以及处理视图层次结构的布局和管理的步骤。
创建和配置视图对象
您可以通过编程方式或使用Interface Builder将视图创建为独立对象,然后将它们组合为视图层次结构以供使用。
使用Interface Builder创建视图对象
创建视图的最简单方法是使用Interface Builder以图形方式组装它们。在Interface Builder中,您可以将视图添加到界面中,将这些视图排列为层次结构,配置每个视图的设置,并将与视图相关的行为连接到您的代码。因为Interface Builder使用实时视图对象(即视图类的实际实例),所以在设计时看到的就是在运行时得到的。然后,将这些活动对象保存在nib文件中,该文件是保留对象状态和配置的资源文件。
通常,您创建nib文件是为了存储应用程序的一个视图控制器的整个视图层次结构。nib文件的顶层通常包含一个视图对象,该对象代表视图控制器的视图。(视图控制器本身通常由文件的所有者对象表示。)顶层视图的大小应适合目标设备,并包含要显示的所有其他视图。很少使用nib文件存储视图控制器的视图层次结构的这一部分。
当将nib文件与视图控制器一起使用时,您要做的就是使用nib文件信息初始化视图控制器。视图控制器在适当的时间处理视图的加载和卸载。但是,如果您的nib文件未与视图控制器关联,则可以使用NSBundle或UINib对象手动加载nib文件内容,这将使用nib文件中的数据来重构您的视图对象。
以编程方式创建视图对象
如果您喜欢以编程方式创建视图,则可以使用标准分配/初始化模式来进行。视图的默认初始化方法是initWithFrame:方法,该方法设置视图相对于其(即将建立)父视图的初始大小和位置。例如,要创建一个新的通用UIView对象,可以使用类似于以下代码:
尽管所有视图都支持initWithFrame:方法,但有些视图可能具有首选的初始化方法,您应该使用用该方法。
创建视图后,必须先将其添加到窗口(或窗口中的另一个视图)中,然后才能显示。
设置视图的属性
UIView类具有几个已声明的属性,用于控制视图的外观和行为。这些属性用于处理视图的大小和位置,视图的透明度,背景色及其渲染行为。所有这些属性都有适当的默认值,您可以在以后根据需要进行更改。您还可以使用“检查器”窗口从“界面生成器”中配置许多这些属性。
下表列出一些更常用的属性(和某些方法)并描述其用法。相关属性一起列出,以便您可以查看影响视图某些方面的选项。
属性
使用
alpha, hidden, opaque
这些属性影响视图的不透明度。 alpha和隐藏属性直接更改视图的不透明度。 opaque属性告诉系统如何合成视图。如果视图的内容是完全不透明的,则将此属性设置为YES,因此不会显示任何基础视图的内容。将此属性设置为YES可以消除不必要的合成操作,从而提高性能。
bounds, frame, center, transform
这些属性影响视图的大小和位置。 center和frame属性表示视图相对于其父视图的位置。该框架还包括视图的大小。 bounds属性在其自己的坐标系中定义视图的可见内容区域。 transform属性用于以复杂的方式设置动画或移动整个视图。例如,您将使用变换来旋转或缩放视图。如果当前变换不是恒等变换,则frame属性是未定义的,应忽略。
autoresizingMask, autoresizesSubviews
这些属性影响视图及其子视图的自动调整大小行为。autoresizingMask属性控制着视图如何响应其父视图的边界变化。 autoresizesSubviews属性控制是否完全调整当前视图的子视图的大小。
contentMode, contentStretch, contentScaleFactor
这些属性影响视图内部内容的呈现行为。contentMode和contentStretch属性确定视图的宽度或高度变化时如何处理内容。仅当您需要自定义高分辨率屏幕的视图的绘制行为时,才使用contentScaleFactor属性。
gestureRecognizers, userInteractionEnabled, multipleTouchEnabled, exclusiveTouch
这些属性影响您的视图处理触摸事件的方式。gestureRecognizers属性包含附加到视图的手势识别器。其他属性控制视图支持的触摸事件。
backgroundColor, subviews, drawRect: method, layer, (layerClass method)
这些属性和方法可帮助您管理视图的实际内容。对于简单视图,您可以设置背景色并添加一个或多个子视图。 subviews属性本身包含一个只读的子视图列表,但是有几种添加和重新排列子视图的方法。对于具有自定义绘图行为的视图,必须重写drawRect:方法。
标记视图以供将来识别
UIView类包含一个标签属性,您可以使用该属性为单个视图对象添加整数值。您可以使用标签在视图层次结构内唯一地标识视图,并在运行时对这些视图执行搜索。(基于标签的搜索比您自己迭代视图层次结构要快。)tag属性的默认值为0。
要搜索带标签的视图,请使用UIView的viewWithTag:方法。此方法对接收器及其子视图执行深度优先搜索。它不搜索超级视图或视图层次结构的其他部分。因此,从层次结构的根视图调用此方法将搜索层次结构中的所有视图,但从特定的子视图调用此方法将仅搜索视图的子集。
创建和管理视图层次结构
管理视图层次结构是开发应用程序用户界面的关键部分。视图的组织结构既影响应用程序的视觉外观,也影响应用程序对更改和事件的响应方式。例如,视图层次结构中的父子关系确定哪些对象可以处理特定的触摸事件。同样,父子关系定义每个视图如何响应界面方向更改。
显示了一个视图分层如何为应用程序创建所需视觉效果的示例。在Clock应用程序的情况下,视图层次结构由不同来源的视图混合而成。标签栏和导航视图是由标签栏和导航控制器对象提供的特殊视图层次结构,用于管理整个用户界面的各个部分。这些条之间的所有内容都属于Clock应用程序提供的自定义视图层次结构。
有多种方法可以在iOS应用程序中构建视图层次结构,包括在Interface Builder中以图形方式在代码中以编程方式。以下各节向您展示如何组装视图层次结构,以及完成后如何在层次结构中查找视图以及如何在不同的视图坐标系之间进行转换。
添加和删除子视图
Interface Builder是构建视图层次结构的最方便的方法,因为您以图形方式组装了视图,查看了视图之间的关系,并确切地看到了这些视图在运行时的外观。使用Interface Builder时,会将结果视图层次结构保存在nib文件中,并在运行时根据需要加载相应的视图。
如果您更喜欢以编程方式创建视图,则可以创建并初始化它们,然后使用以下方法将它们排列为层次结构:
要将子视图添加到父视图,请调用父视图的addSubview:方法。此方法将子视图添加到父级子视图列表的末尾。
要在父级子视图列表的中间插入子视图,请调用父级视图的任何insertSubview方法。在列表的中间插入一个子视图以可视方式将视图放置在列表后面的所有视图之后。
要对父视图中的现有子视图重新排序,请调用父视图的BringSubviewToFront :sendSubviewToBack:或exchangeSubviewAtIndex:withSubviewAtIndex:方法。使用这些方法比删除子视图并重新插入它们要快。
要从其父视图中删除子视图,请调用子视图(不是父视图)的removeFromSuperview方法。
将子视图添加到其父视图时,该子视图的当前框架矩形表示其在父视图内部的初始位置。默认情况下,子视图的框架位于其父视图的可见范围之外不裁剪。如果您想将子视图裁剪到超级视图的范围,则必须将超级视图的clipsToBounds属性显式设置为YES。
您可以在视图层次结构中添加子视图的一个地方是视图控制器的loadView或viewDidLoad方法。如果要以编程方式构建视图,则将视图创建代码放入视图控制器的loadView方法中。无论是以编程方式创建视图还是从nib文件加载视图,都可以在viewDidLoad方法中包含其他视图配置代码。
显示了UIKit目录(iOS)中TransitionsViewController类的viewDidLoad方法:创建和自定义UIKit控件示例应用程序。TransitionsViewController类管理与两个视图之间的过渡相关联的动画。该应用程序的初始视图层次结构(由根视图和工具栏组成)是从nib文件加载的。viewDidLoad方法中的代码随后创建用于管理过渡的容器视图和图像视图。容器视图的目的是简化实现两个图像视图之间的过渡动画所需的代码。容器视图本身没有真正的内容。
实例代码:
超级视图会自动保留其子视图,因此在嵌入子视图之后,可以安全地释放该子视图。实际上,建议这样做,因为这会阻止应用程序将视图一次保留太多,并在以后导致内存泄漏。只需记住,如果您从子视图的子视图中删除了一个子视图并打算重用它,则必须再次保留该子视图。removeFromSuperview方法会在将子视图从其父视图中删除之前自动释放该子视图。如果在下一个事件循环周期之前未保留视图,则将释放该视图。
将子视图添加到另一个视图时,UIKit会同时通知父视图和子视图此更改。如果您实现自定义视图,则可以通过覆盖willMoveToSuperview:,willMoveToWindow:,willRemoveSubview:,didAddSubview:,didMoveToSuperview或didMoveToWindow方法中的一个或多个来拦截这些通知。您可以使用这些通知来更新与视图层次结构相关的任何状态信息,或执行其他任务。
创建视图层次结构后,可以使用视图的superview和subviews属性以编程方式导航它。每个视图的window属性都包含当前显示该视图的窗口(如果有)。由于视图层次结构中的根视图没有父视图,因此其superview属性设置为nil。对于当前在屏幕上的视图,窗口对象是视图层次结构的根视图。
隐藏视图
要以视觉方式隐藏视图,可以将其hidden属性设置为YES或将其alpha属性更改为0.0。隐藏视图不会从系统接收触摸事件。但是,隐藏视图确实会参与自动调整大小以及与视图层次结构关联的其他布局操作。因此,隐藏视图通常是从视图层次结构中删除视图的一种方便的替代方法,特别是如果您计划很快在某个时候再次显示视图。
如果隐藏当前是第一响应者的视图,则该视图不会自动退出其第一响应者状态。针对第一个响应者的事件仍会传递到隐藏视图。为防止这种情况发生,您应在隐藏视图时强制视图退出第一响应者状态。
如果您想为视图从可见到隐藏(或相反)的过渡设置动画,则必须使用视图的alpha属性。hidden属性不是可设置动画的属性,因此您对其进行的任何更改都会立即生效。
在视图层次结构中定位视图
有两种方法可以在视图层次结构中定位视图:
将指向任何相关视图的指针存储在适当的位置,例如在拥有视图的视图控制器中。
为每个视图的标签属性分配一个唯一的整数,然后使用viewWithTag:方法进行定位。
存储对相关视图的引用是查找视图的最常用方法,并且使访问这些视图变得非常方便。如果您使用Interface Builder创建视图,则可以使用outlets将nib文件中的对象(包括代表管理控制器对象的File Owner对象)相互连接。对于以编程方式创建的视图,可以将对这些视图的引用存储在私有成员变量中。无论您使用outlet还是私有成员变量,您都有责任根据需要保留视图,然后也将其释放。确保正确保留和释放对象的最佳方法是使用声明的属性。
标签是减少硬编码依赖性并支持更多动态和灵活解决方案的有用方法。与其存储指向视图的指针,不如使用其标记定位它。标签也是引用视图的一种更持久的方式。例如,如果要保存应用程序中当前可见的视图列表,则可以将每个可见视图的标记写到文件中。这比存档实际的视图对象更简单,尤其是在仅跟踪当前可见的视图的情况下。随后加载应用程序时,您将重新创建视图并使用保存的标签列表设置每个视图的可见性,从而将视图层次结构返回到其先前的状态。
平移,缩放和旋转视图
每个视图都有一个关联的仿射变换,可用于平移,缩放或旋转视图的内容。视图变换会改变视图的最终渲染外观,通常用于实现滚动,动画或其他视觉效果。
UIView的transform属性包含一个CGAffineTransform结构,其中包含要应用的转换。默认情况下,此属性设置为Identity转换,它不会修改视图的外观。您可以随时为该属性分配新的变换。例如,要将视图旋转45度,可以使用以下代码:
将前面代码中的变换应用于视图将使该视图绕其中心点顺时针旋转。下图显示了将这种转换应用于嵌入在应用程序中的图像视图后的外观。
将多个转换应用于视图时,将这些转换添加到CGAffineTransform结构的顺序很重要。旋转视图然后进行平移与旋转视图然后进行旋转不同。即使每种情况下的旋转和平移量相同,转换的顺序也会影响最终结果。此外,您添加的所有变换都将相对于视图的中心点应用到视图。因此,应用旋转因子将视图围绕其中心点旋转。缩放视图会更改视图的宽度和高度,但不会更改其中心点。
在视图层次结构中转换坐标
在不同的时间,特别是在处理事件时,应用程序可能需要将坐标值从一个参考系转换为另一个参考系。例如,触摸事件报告了每次触摸在窗口坐标系中的位置,但是视图对象通常需要在视图的本地坐标系中获得该信息。UIView类定义了以下用于在视图的本地坐标系之间进行坐标转换的方法:
convertPoint:fromView:
convertRect:fromView:
convertPoint:toView:
convertRect:toView:
convert ...:fromView:方法将坐标从其他视图的坐标系转换为当前视图的局部坐标系(边界矩形)。相反,convert ...:toView:方法会将坐标从当前视图的局部坐标系(边界矩形)转换为指定视图的坐标系。如果您将nil指定为任何方法的参考视图,则将在包含该视图的窗口的坐标系中进行转换。
除了UIView转换方法外,UIWindow类还定义了几种转换方法。这些方法与UIView版本相似,不同之处在于,这些方法不是在视图的本地坐标系之间来回转换,而是在窗口的坐标系之间来回转换。
convertPoint:fromWindow:
convertRect:fromWindow:
convertPoint:toWindow:
convertRect:toWindow:
在旋转视图中转换坐标时,UIKit在您希望返回的矩形反映源矩形覆盖的屏幕区域的假设下转换矩形。旋转如何导致矩形大小在转换期间发生变化的示例。在该图中,外部父视图包含一个旋转的子视图。将子视图的坐标系中的矩形转换为父视图的坐标系,将得到一个实际上更大的矩形。这个更大的矩形实际上是在外部视图范围内的最小矩形,它完全包围了旋转的矩形。
在运行时调整视图的大小和位置
每当视图大小改变时,其子视图的大小和位置都必须相应地改变。UIView类支持视图层次结构中视图的自动和手动布局。使用自动布局,您可以设置每个视图在调整其父视图大小时应遵循的规则,然后完全忽略调整操作的大小。使用手动布局,您可以根据需要手动调整视图的大小和位置。
为布局更改做准备
每当视图中发生以下任何事件时,都可能发生布局更改:
视图边界矩形的大小会发生变化。
界面方向发生变化,通常会触发根视图的边界矩形的变化。
与视图图层关联的一组核心动画子图层会发生变化并需要布局
您的应用程序通过调用视图的setNeedsLayout或layoutIfNeeded方法强制进行布局。
您的应用程序通过调用视图基础层对象的setNeedsLayout方法来强制布局。
使用自动调整大小规则自动处理布局更改
更改视图的大小时,通常需要更改任何嵌入式子视图的位置和大小,以考虑其父级的新大小。超级视图的autoresizesSubviews属性确定子视图是否完全调整大小。如果此属性设置为YES,则视图使用每个子视图的autoresizingMask属性来确定如何调整该子视图的大小和位置。更改任何子视图的大小都会为其嵌入式子视图触发类似的布局调整。
对于视图层次结构中的每个视图,将该视图的autoresizingMask属性设置为适当的值是处理自动布局更改的重要部分。下表列出了可应用于给定视图的自动调整大小选项,并描述了它们在布局操作期间的效果。您可以使用OR运算符组合常量,也可以在将常量分配给autoresizingMask属性之前将它们添加在一起。如果要使用Interface Builder组装视图,则可以使用“自动调整大小”检查器来设置这些属性。
| 自动调整掩码 | 描述 | | UIViewAutoresizingNone | 该视图不会自动调整大小。 (这是默认值。)| | UIViewAutoresizingFlexibleHeight | 当超级视图的高度发生变化时,视图的高度也会发生变化。如果不包含此常数,则视图的高度不会改变。| | UIViewAutoresizingFlexibleWidth | 当超级视图的宽度改变时,视图的宽度也会改变。如果不包含此常数,则视图的宽度不会改变。 | | UIViewAutoresizingFlexibleLeftMargin | 视图的左边缘和超级视图的左边缘之间的距离会根据需要增大或缩小。如果不包含此常数,则视图的左边缘与超级视图的左边缘保持固定的距离。 | | UIViewAutoresizingFlexibleRightMargin | 视图右边缘和超级视图右边缘之间的距离会根据需要增加或缩小。如果不包含此常数,则视图的右边缘与超级视图的右边缘保持固定的距离。 | | UIViewAutoresizingFlexibleBottomMargin | 视图的底部边缘和超级视图的底部边缘之间的距离会根据需要增加或缩小。如果不包含此常数,则视图的底边缘与超级视图的底边缘保持固定的距离。 | | UIViewAutoresizingFlexibleTopMargin | 视图的顶部边缘和超级视图的顶部边缘之间的距离会根据需要增加或缩小。如果不包含此常数,则视图的顶部边缘与超级视图的顶部边缘保持固定的距离。 |
下图显示了自动调整大小蒙版中的选项如何应用于视图的图形表示。给定常数的存在表示视图的指定方面是灵活的,并且在更改父视图的边界时可能会更改。如果没有常量,则表示该视图的布局在该方面是固定的。当您配置一个沿一个轴具有多个柔性属性的视图时,UIKit会在相应空间之间平均分配任何大小更改。
配置自动调整大小规则的最简单方法是使用Interface Builder大小检查器中的“自动调整大小”控件。上图中的灵活宽度和高度常数与“自动调整大小”控件图中的宽度和尺寸指示器具有相同的行为。但是,保证边界指标的行为和使用被有效地扭转了。在Interface Builder中,边距指示器的存在意味着边距的尺寸是固定的,而没有指示器则意味着边距具有灵活的尺寸。幸运的是,Interface Builder提供了一个动画,向您展示自动调整大小行为的更改如何影响您的视图。
如果视图的transform属性不包含恒等变换,则该视图的框架未定义,其自动调整大小行为的结果也未定义。
在对所有受影响的视图应用自动调整大小规则之后,UIKit会返回并为每个视图提供一个机会对其超级视图进行任何必要的手动调整。
手动调整视图的布局
每当视图大小改变时,UIKit都会应用该视图子视图的自动调整大小行为,然后调用该视图的layoutSubviews方法以使其进行手动更改。当自动调整大小行为本身不能产生所需结果时,可以在自定义视图中实现layoutSubviews方法。您对该方法的实现可以执行以下任一操作:
调整任何直接子视图的大小和位置。
添加或删除子视图或核心动画层。
通过调用子视图的setNeedsDisplay或setNeedsDisplayInRect:方法来强制重绘子视图。
应用程序经常手动布置子视图的一个地方是在实现较大的可滚动区域时。由于要为其滚动内容提供单个大视图是不切实际的,因此应用程序通常会实现包含许多较小的切片视图的根视图。发生滚动事件时,根视图将调用其setNeedsLayout方法来启动布局更改。然后,其layoutSubviews方法根据发生的滚动量重新定位图块视图。当磁贴滚动到视图的可见区域之外时,layoutSubviews方法将磁贴移动到传入边缘,从而在处理过程中替换其内容。
编写布局代码时,请确保通过以下方式测试代码:
更改视图的方向,以确保布局在所有受支持的界面方向上都正确。
确保您的代码对状态栏高度的更改做出了适当的响应。通话时,状态栏的高度会增加,而用户结束通话时,状态栏的大小会减少。
在运行时修改视图
当应用程序从用户接收输入时,它们会响应于该输入来调整其用户界面。应用程序可能会通过重新排列视图,更改其大小或位置,隐藏或显示它们或加载一组全新的视图来修改其视图。在iOS应用程序中,可以通过几种方式来执行这些操作:
在视图控制器中:
视图控制器必须先创建其视图,然后再显示它们。它可以从nib文件加载视图或以编程方式创建视图。当不再需要这些视图时,它将丢弃它们。
当设备更改方向时,视图控制器可能会调整视图的大小和位置以进行匹配。作为新方向调整的一部分,它可能会隐藏一些视图并显示其他视图。
当视图控制器管理可编辑的内容时,它可能会在进入和退出编辑模式时调整其视图层次结构。例如,它可能会添加额外的按钮和其他控件,以便于编辑其内容的各个方面。这可能还需要调整任何现有视图的大小,以容纳额外的控件。
在动画块中:
如果要在用户界面中的不同视图集之间切换,则可以从动画块内部隐藏某些视图,并显示其他视图。
实施特殊效果时,可以使用动画块来修改视图的各种属性。例如,要对视图大小进行动画处理,可以更改其框架矩形的大小。
其他方法:
发生触摸事件或手势时,您的界面可能会通过加载一组新视图或更改当前视图来响应。
当用户与滚动视图交互时,较大的可滚动区域可能会隐藏并显示图块子视图。
出现键盘时,您可以重新定位视图或调整视图大小,以使它们不位于键盘下方。
视图控制器是启动对视图进行更改的常见位置。因为视图控制器管理与正在显示的内容关联的视图层次结构,所以它最终负责那些视图上发生的所有事情。在加载其视图或处理方向更改时,视图控制器可以添加新视图,隐藏或替换现有视图,并进行任意数量的更改以使视图可用于显示。而且,如果您实现了对视图内容进行编辑的支持,则UIViewController中的setEditing:animated:方法将为您提供一个在视图与可编辑版本之间进行过渡的地方。
动画块是发起与视图相关的更改的另一个常见位置。 UIView类中内置的动画支持使对视图属性进行更改的动画变得容易。您也可以使用transitionWithView:duration:options:animations:completion:或transitionFromView:toView:duration:options:completion:方法将整个视图集换成新视图集。
与核心动画层交互
每个视图对象都有一个专用的“核心动画”层,用于管理屏幕上视图内容的呈现和动画。尽管您可以对视图对象进行很多处理,但也可以根据需要直接使用相应的图层对象。视图的图层对象存储在视图的layer属性中。
更改与视图关联的图层类
创建视图后,无法更改与视图关联的图层类型。因此,每个视图都使用layerClass类方法指定其图层对象的类。此方法的默认实现返回CALayer类,并且更改此值的唯一方法是子类,覆盖该方法并返回另一个值。您可以更改此值以使用其他类型的图层。例如,如果视图使用平铺显示大的可滚动区域,则可能要使用CATiledLayer类来支持视图。
layerClass方法的实现应只创建所需的Class对象并返回它。例如,使用平铺的视图对此方法将具有以下实现:
每个视图在其初始化过程的早期就调用其layerClass方法,并使用返回的类创建其图层对象。此外,视图始终将自身分配为其图层对象的委托。此时,视图拥有其图层,并且视图与图层之间的关系不得更改。您也不得分配与任何其他图层对象的委托相同的视图。更改视图的所有权或委托关系将导致绘图问题和应用程序潜在的崩溃。
在视图中嵌入图层对象
如果您希望主要使用图层对象而不是视图,则可以根据需要将自定义图层对象合并到视图层次结构中。定制层对象是视图不拥有的CALayer的任何实例。通常,您可以通过编程方式创建自定义图层,然后使用Core Animation例程将其合并。自定义图层不会接收事件或参与响应者链,但会根据Core Animation规则绘制自身并响应其父视图或图层中的尺寸更改。
下段代码显示了一个视图控制器中的viewDidLoad方法的示例,该方法创建一个自定义图层对象并将其添加到其根视图。该层用于显示动画的静态图像。无需将图层添加到视图本身,而是将其添加到视图的基础层。
如果需要,您可以添加任意数量的子层并将其安排到子层层次结构中。但是,在某些时候,这些图层必须附加到视图的图层对象。
定义自定义视图
如果标准系统视图不能完全满足您的需求,则可以定义一个自定义视图。自定义视图使您可以完全控制应用程序内容的外观以及与该内容的交互的处理方式。
如果使用OpenGL ES进行绘制,则应使用GLKView类而不是对UIView进行子类化。
实施自定义视图的清单
自定义视图的工作是呈现内容并管理与该内容的交互。但是,成功实现自定义视图不仅仅涉及绘制和处理事件。以下清单包含实现自定义视图时可以覆盖的更重要的方法(以及可以提供的行为):
为您的视图定义适当的初始化方法:
对于计划以编程方式创建的视图,请覆盖initWithFrame:方法或定义自定义初始化方法。
对于计划从笔尖文件加载的视图,请重写initWithCoder:方法。使用此方法初始化视图并将其置于已知状态。
实现一个dealloc方法来处理所有自定义数据的清理。
要处理任何自定义图形,请覆盖drawRect:方法并在那里进行图形绘制。
设置视图的autoresizingMask属性以定义其自动调整大小行为。
如果您的视图类管理一个或多个整体子视图,请执行以下操作:
在视图的初始化序列中创建这些子视图。
在创建时设置每个子视图的autoresizingMask属性。
如果您的子视图需要自定义布局,请覆盖layoutSubviews方法并在那里实现布局代码。
要处理基于触摸的事件,请执行以下操作:
使用addGestureRecognizer:方法将任何合适的手势识别器附加到视图。
对于要自己处理触摸的情况,请重写touchesBegan:withEvent :、 touchesMoved:withEvent :、 touchesEnded:withEvent:和touchesCancelled:withEvent:方法。(请记住,无论您重写了其他与触摸相关的其他方法,都应始终重写touchesCancelled:withEvent:方法。)
如果希望视图的打印版本与屏幕版本看起来不同,请实现drawRect:forViewPrintFormatter:方法。
除了覆盖方法之外,请记住,视图的现有属性和方法可以做很多事情。例如,contentMode和contentStretch属性使您可以更改视图的最终呈现外观,可能比自己重绘内容更可取。除了UIView类本身之外,您还可以直接或间接配置视图基础CALayer对象的许多方面。您甚至可以更改图层对象本身的类。
初始化您的自定义视图
您定义的每个新视图对象都应包含一个自定义的initWithFrame:初始化方法。此方法负责在创建时初始化类,并将视图对象置于已知状态。在代码中以编程方式创建视图实例时,可以使用此方法。
下段代码显示了标准initWithFrame:方法的基本实现。此方法首先调用该方法的继承的实现,然后在返回初始化的对象之前初始化实例变量和类的状态信息。传统上,首先会调用继承的实现,因此,如果出现问题,您可以中止自己的初始化代码并返回nil。
如果打算从nib文件加载自定义视图类的实例,则应注意,在iOS中,该nib加载代码不使用initWithFrame:方法实例化新的视图对象。而是使用NSCoding协议中的initWithCoder:方法。
即使您的视图采用NSCoding协议,Interface Builder也不知道您视图的自定义属性,因此不会将这些属性编码到nib文件中。结果,您自己的initWithCoder:方法应执行它可以将视图置于已知状态的所有初始化代码。您还可以在视图类中实现awakeFromNib方法,并使用该方法执行其他初始化。
实现绘图代码
对于需要进行自定义绘图的视图,您需要重写drawRect:方法并在那里进行绘图。建议仅将自定义图纸作为最后的选择。通常,如果可以使用其他视图来呈现您的内容,那是首选。
drawRect:方法的实现应该做的只是一件事:绘制内容。此方法不是更新应用程序数据结构或执行与绘图无关的任何任务的地方。它应配置绘图环境,绘制内容并尽快退出。而且,如果您的drawRect:方法可能经常被调用,则应尽一切努力优化绘图代码,并在每次调用该方法时尽可能少地绘制。
在调用视图的drawRect:方法之前,UIKit为视图配置基本的绘图环境。具体来说,它创建图形上下文并调整坐标系和裁剪区域以匹配视图的坐标系和可见范围。因此,在调用drawRect:方法时,您可以开始使用UIKit和Core Graphics等本机绘制技术来绘制内容。您可以使用UIGraphicsGetCurrentContext函数获得指向当前图形上下文的指针。
当前的图形上下文仅在一次调用视图的drawRect:方法期间有效。UIKit可能会为每次对该方法的后续调用创建一个不同的图形上下文,因此您不应尝试缓存该对象并在以后使用它。
下段代码显示了drawRect:方法的简单实现,该方法在视图周围绘制了10像素宽的红色边框。因为UIKit绘图操作将Core Graphics用于其基础实现,所以您可以混合绘图调用(如下所示)以获得期望的结果。
如果您知道视图的绘图代码始终使用不透明的内容覆盖视图的整个表面,则可以通过将视图的opaque属性设置为YES来提高系统性能。当您将视图标记为不透明时,UIKit会避免绘制紧接在视图后面的内容。这不仅减少了绘制时间,而且使将视图与其他内容合成在一起所需的工作减少了。但是,仅当您知道视图的内容完全不透明时,才应将此属性设置为YES。如果您的视图不能保证其内容始终是不透明的,则应将该属性设置为NO。
提高绘图性能(尤其是在滚动过程中)的另一种方法是将视图的clearsContextBeforeDrawing属性设置为NO。当此属性设置为YES时,UIKIt会在调用方法之前用透明的黑色自动填充您的drawRect:方法要更新的区域。将此属性设置为NO可以消除该填充操作的开销,但是会给您的应用程序增加负担,以使内容填充传递给drawRect:方法的更新矩形。
响应事件
视图对象是响应者对象(UIResponder类的实例),因此能够接收触摸事件。当发生触摸事件时,窗口会将相应的事件对象调度到发生触摸的视图。如果您的视图对事件不感兴趣,则可以忽略该事件或将其传递到响应者链上,以由其他对象处理。
除了直接处理触摸事件外,视图还可以使用手势识别器来检测轻击,滑动,捏和其他类型的常见触摸相关手势。手势识别器会进行艰苦的工作来跟踪触摸事件,并确保它们遵循正确的标准以使它们成为目标手势。您无需创建应用程序即可跟踪触摸事件,而可以创建手势识别器,为其分配适当的目标对象和操作方法,然后使用addGestureRecognizer:方法将其安装在视图上。然后,当相应的手势发生时,手势识别器就会调用您的操作方法。
如果您希望直接处理触摸事件,则可以为视图实现以下方法:
touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
视图的默认行为是一次仅响应一次触摸。如果用户将另一根手指放下,系统将忽略触摸事件,并且不会将其报告给您的视图。如果您打算通过视图的事件处理程序方法跟踪多指手势,则需要通过将视图的multiTouchEnabled属性设置为YES来启用多点触摸事件。
某些视图(例如标签和图像)最初完全禁用事件处理。您可以通过更改视图的userInteractionEnabled属性的值来控制该视图是否能够接收触摸事件。您可以将此属性临时设置为NO,以防止用户在长操作挂起时操纵视图的内容。为了防止事件到达您的任何视图,您还可以使用UIApplication对象的beginIgnoringInteractionEvents和endIgnoringInteractionEvents方法。这些方法不仅影响单个视图,还影响整个应用程序的事件传递。
在处理触摸事件时,UIKit使用UIView的hitTest:withEvent:和pointInside:withEvent:方法来确定触摸事件是否在给定视图范围内发生。尽管您很少需要重写这些方法,但是您可以这样做来为视图实现自定义触摸行为。例如,您可以重写这些方法以防止子视图处理触摸事件。
清理视图
如果您的视图类分配了任何内存,存储了对任何自定义对象的引用或保存了在释放视图时必须释放的资源,则必须实现dealloc方法。当您的视图的保留计数达到零时,系统便会调用dealloc方法,该是时候取消分配视图了。此方法的实现应释放视图持有的所有对象或资源,然后调用继承的实现,如下段代码所示。您不应使用此方法执行任何其他类型的任务。
Last updated
Was this helpful?