定义一个自定义的视图
如果系统的视图不能满足你的需要,你可以自定义一个视图。在自定义视图里你可以完全控制你的应用程序的内容的外观和与内容交互的处理方式。
注意如果你使用OpenGL ES来绘制。你应该使用GLK类而你搜UIView的子类。更多关于如何让使用OpenGL ES绘制的信息,查看OpenGL ES Programming Guide for iOS.
实现一个自定义视图你需要做的事
自定义视图的职责是去呈现内容并管理与内容相关的交互。一个成功的自定义视图不仅包含绘制和处理时间的功能。下面这个核对清单包含了在你实现一个自定义视图时,你应该覆盖的最重要的一些方法(和一些你应该提供的行为):
- 为你的自动义视图定义合适的初始化方法
- 对于你想要用代码创建的视图,覆盖
initWithFrame
方法,或者定义一个自定义的初始化方法。 - 对于你想要从nib文件中加载的视图,覆盖
initWithCode
方法.使用这个方法去初始化你的视图并把它放到一个已知状态
- 对于你想要用代码创建的视图,覆盖
- 实现一个
dealloc
方法来处理任何自定义数据的清理 - 设置视图的autoresizingMask属性来定义这个视图的自动调整大小的行为。
- 为了处理任何自定义的绘制,应该覆盖
drawRect
方法,在这个方法里写你的绘制代码 - 设置你的视图类管理一个或多个子视图,按照下面的方法做:
- 在你的view的初始化队列里创建这些视图
- 在创建的这些子视图时为每一个子视图设置autoresizingMask属性
- 如果你的子视图需要自定义的布局,覆盖
layoutSubviews
方法并在其中实现你的布局代码
- 为了处理基于触摸的时间,按照下面的方法做:
- 通过
addGestureRecognizer:
方法将任何适合的手势识别 附加到这个视图上 - 对于你想要自己处理触摸时间的情况,覆盖
touchesBegan:withEvent:
,touchesMoved:withEvent:
,touchesEnded:withEvent:
, 和touchesCancelled:withEvent:
方法。
- 通过
- 如果你想要查看你自定义视图的印刷版本和在屏幕上显示的版本有什么不同,你应该实现
drawRect:forViewPrintFormatter:
方法。更多关于如何在你的view里支持打印的细节信息,查看Drawing and Printing Guide for iOS.
此外,在覆盖这些方法时,记得你可以使用这个view现有的属性和方法来做许多工作。比如,contentMode属性和contentStretch属性可以让你改变你视图的最终渲染外观,并且这样做可能比你重绘视图内容性能和效果更好。此外,对于UIView类,一个视图的许多方面是基于你可以直接或间接配置的CALayer对象的。你甚至可以改变layer对象的类。
初始化你的自动义视图
你定义的每一个视图对象都应该包含一个自定义的initWithFrame
初始化方法。这个方法负责在创建的时候初始化这个类并且把你的视图对象放置到一个已知的状态。在用代码创建你自定义视图的实例时,使用这个方法。
listing 3-3显示了一个标准的initWithFrame
方法的实现骨架。这个方法首先调用了继承来的initWithFrame
方法,然后在返回初始化后的对象钱初始化实例变量和这个类的状态信息。调用继承来的initWithFrame
实现是首先要做的事,这样一旦这里出现了问题,你可以终止你的初始化代码并返回nil。
listing 3-3
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
// setup the initial properties of the view
...
}
return self;
}
如果你想要从一个nib文件里加载你自定义视图的实例,你应该注意,在ios中,nib加载代码不会使用initWithFrame
方法去实例化新的视图对象。作为代替,他将会使用(为NSCoding协议中的一部分的)initWithCode
方法来实例化新的试图对象。
即使你的视图遵守NSCoding协议,InterFace Bileder并不知道你视图的自定义属性,所以将不会把这些属性编码到nib文件。因此,你自定义的initWithCode
方法应该执行可以把这个view配置到已知状态的代码。你还可以在你的视图类中实现awakeFromNib
方法,并使用这个方法去执行额外的初始化操作。
实现你自定义的绘制代码
对于那些需要自定义绘制的视图,你需要覆盖drawRect
方法并在这个方法里写你的绘制代码。没有特殊的必要,不要是同自定义绘制。一般来说,你可以使用其他视图来表现你的内容,这是被鼓励使用的。
你drawRect
方法的实现里应该只做一件事,绘制你的内容。这个方法不应该去更新的应用程序的数据结构或执行其他与绘制无关的任务。这个方法应该配置绘制欢迎,绘制内容,并尽可能快的退出。当你的drawRect
方法可能被频繁的调用时,你应该进你所有的可能去优化你的绘制代码并早每次被调用时尽可能少的绘制。
在调用的定义视图的drawRect
方法之前,UIKit为你的视图配置基本的绘制环境。特别的,UIKite会创建一个图形上下文、调整坐标系统、剪切区域来与这个坐标系统匹配的,并使你的视图的边界可见。这样,在你的drawRect
被调用时,你可以使用本地的绘制技术绘制你的内容,比如UIKit和Core Graphics。你可以通过UIGraphicsGetCurrentContext
方法来得到当前的图形上下文的指针。
重要当前的图形上下文只在调用drawRect
的过程中是有效的。UIkit可能为每一个调用这个方法的子序列创建不同的图形上下文,所以你不应该去缓存这个图形上下文并在之后调用它。
listing 3-4显示了一个drawRect
方法的简单实现,这个实现为这个视图绘制了一个10像素宽的边界。因为UIKit绘制是使用基于Core Graphic实现方法的,所以你可以混合绘制调用去得到你想要的结果(就像在这里展示的那样)
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect myFrame = self.bounds;
// Set the line width to 10 and inset the rectangle by
// 5 pixels on all sides to compensate for the wider line.
CGContextSetLineWidth(context, 10);
CGRectInset(myFrame, 5, 5);
[[UIColor redColor] set];
UIRectFrame(myFrame);
}
如果你知道你的视图绘制代码总是会用不透明的内容覆盖整个视图的表面,你可以通过设置你视图的opaque属性为YES来提高系统的性能。当你把一个视图标记为不透明的,UIKit会避免在你view之下的人位置处绘制内同。这不仅减少了绘制话费的时间,同时还减少了把你的view与其他内容组合在一起的工作(这个工作是必须做的)。然而,你应该在你视图的内容是完全不透明的时候才把这个属性设置为YES。如果你的视图不能保证它所有的内容都是不透明的,你应该把这个属性设置为NO。
另一个提高绘制性能的方法,尤其是在滚动的时候,是设置你视图的clearsContextBeforeDrawing 属性为NO。当这个属性被设置为YES是,在你调用drawRect
方法时,UIKit自动用透明的黑色填满这个将要被你的drawRect
方法更新的区域。吧这个属性设置为NO,消除了填充操作的开销,但是把这个负担交给你的应用程序,去用内容填充传递到你drawRect
方法的最新的矩形。
相应时间
视图对象是一个responder对象(UIResponder类的实例),因此可以接受触摸事件。当一个触摸事件发生时,window发送这个相应的事件对象到到触摸发生的视图。如果你的视图对这个事件不感兴趣,它可以忽视这个事件或者把这个事件传递到相应链中去让另一个对象处理。
此外,为了直接处理触摸事件,视图可以使用手势识别去检测tap,swipe,pinch和其他通用的与触摸相关的收拾。手势识别做追踪触摸事件和保证他们遵循了正确的标准去保证他们是目标手势。作为你的应用程序必须追踪触摸事件的代替,你可以创建手势识别,为这个手势分配一个合适的目标对象和动作方法,用addGestureRecognizer:
方法把这个手势识别加载到你的视图上。当这个手势识别相应的手势发生时,这个手势识别会调用你的动作代码。
如果你喜欢直接处理触摸事件,你可以为你的视图实现以下的方法,这些方法的细节是在 Event Handling Guide for iOS被描述的:
- touchesBegan:withEvent:
- touchesMoved:withEvent:
- touchesEnded:withEvent:
- touchesCancelled:withEvent:
视图的默认行为是去一次只响应一个触摸。如果用户把第二根手指放上去,系统会忽略这个触摸事件并不会把这个事件报告给你的视图。如果你从你视图的事件处理方法中计划追踪多手指手势,你需要通过设置的视图的multipleTouchEnabled 属性为YES来使多点触摸事件可用。
一些视图,比如Label和image,初始化时就不用于事件处理。你可以通过改变视图的userInteractionEnabled 属性来控制一个视图是否接受触摸事件。当一个长操作发生时,你可能暂时把这个属性设置为NO来阻止用户操作你视图的内容,你同样可以使用UIApplication对象的beginIgnoringInteractionEvents
和endIgnoringInteractionEvents
方法。这些方法影响整个应用程序的事件传递,并不是值影响一个单独的视图。
**注意**UIView的动画方法通常会在动画发生时使触摸事件无效。你可以通过合适的配置动画来覆盖这个行为。更多关于表现动画的信息,请查看Animations.
当处理触摸时事件,UIKit使用UIView的hitTest:withEvent:
和pointInside:withEvent:
方法来决定一个触摸事件是否是在一个给定视图的边界内部发生的。即使你不经常需要覆盖这些犯法,你可以这样做去为你的视图实现自定义的触摸行为 。比如,你可以覆盖这些方法来阻止子视图处理触摸事件。
清理视图
当你的视图分配了内存,储存了其他自定义对象的引用,或者拥有在这个视图被release时必须被release的源,你必须实现dealloc
方法,当你的视图的retain计数为0时,系统调用dealloc
方法去deallocate这个视图。你对该方法实现应该release任何被这个view拥有的对象或源,然后调用继承来的实现,就像在listing 3-5显示的那样。你不应该使用这个方法去执行其他类型的任务。
listing 3-5
- (void)dealloc{
//release a retained UIColor object
[color release];
//call the inherited implementation
[super dealloc];
}
动画
动画提供了在用户界面不同状态间的流动、可视的过渡。在iOS中,动画随处可见,用来调整的位置,改变视图的大小,从视图层次结构中移除视图,隐藏视图。你可能使用动画去为用户呈现反馈或实现有趣的可视效果。
在iOS中,创建精美的动画不需要你写绘制代码。所有的在本章中描述的动画技术都是使用了Core Animation内建的动画支持。所有你需要做的就是触发这个动画,并使Core Animation处理对一个单独的frame的渲染。这使得创建精美的动画变得十分简单,用几行代码就可以实现。
什么可以用动画显示?
UIKit和Core Animation都提供了对动画的支持,但是没种基础提供支持的层面却不一样。在UIKit中,动画是使用UIView对象来呈现的。视图支持一个基本的动画集,这个动画集包含了许多常见的任务。比如说,你可以用动画改变视图的属性或者使用过渡动画来用一个视图集替换另一个视图集。
表4-1列出了可以动画显示的UIView类的属性(有内建动画支持的属性)。可以被动画显示并不意味着这些动画将会自动发生。改变这些属性的值通常只会立即更新这些属性(或试图),没有动画显示这些改变。为了用动画显示这些改变,你必须改从一个动画block内部变这些属性的值,这会在 Animating Property Changes in a View一节中被描述。
表4-1
属性 | 你可以做的改变 |
---|---|
frame | 修改这个属性来改变视图的大小和位置(在父视图坐标系下)。如果transform属性不包含恒等变换,修改bounds属性或center属性来代替修改frame属性 |
bounds | 修改这个值来改变视图的大小 |
center | 修改这个值来改变视图的位置(在父视图坐标系啊) |
transform | 修改这个值去缩放旋转或位移一个视图(以center为基准)变化。使用这个属性做的变换通常是在2D空间里表现的。(为了表现3D的变换,你必须用Core Animation来动画显示视图的layer对象) |
alpah | 修改这个属性去慢慢改变视图的透明性 |
backgroundColor | 修改这个属性去改变视图的北京颜色 |
contentStretch | 修改这个属性去改变视图内容拉升去填充可用区域的方式 |
用动画显示视图的过渡是一个在viewController提供的方式之上去改变你视图层次结构方式。尽管你应该使用view controller来管理简明的视图层次结构,但是有时你会想要替换整个视图层次结构或视图层次结构的一部分。在这种情况下,你可以使用基于视图的过度来动画显示视图的添加或移除。
在你想要表现更多精妙动画的地方,或者想要使用UIView没有提供动画,你可以使用Core Animation和在视图直线的layer对象来创建一个动画。因为视图和layer对象是错综复杂的联系在一起的,改变一个视图的层会影响视图本身。使用Core Animation,你可以用动画显示如下种类的对视图layer对象的改变。
- layer的大小和位置
- 在展现变换的时候用的重点
- 在3D空间内变换layer或sublayer
- 从layer层次结构中添加或移除某个layer
- layer相对于其它兄弟姐妹layer的Z 顺序
- layer的阴影
- layer的边界(无论layer是否是圆角的)
- 在重调大小操作中层拉伸的部分
- layer的透明性
- 对在layer边界外的子层的剪切行为
- layer当前的内容
- layer的栅格化行为
注意如果你的视图拥有一个自定义的layer对象(这个对象没有被关联到视图)你必须使用CoreAnimation来动画显示任何对其的改变。
尽管本章讲演了一些Core Animation 行为,但是它是通过在你的视图代码中初始化它们来实现的。关于如何使用Core Animaiton 来动画层的完整信息,请看 Core Animation Programming Guide 和 Core Animation Cookbook.