本文是Apple Documentation Archive View Programming Guide的学习笔记。
自定义视图的高级主题
前一章节讨论自定义视图中遇到的一般性话题,这一章节讨论个别话题。
视图所绘制的图像,常常因为用户交互过程中产生的行为,而需要暂时性修改。比如鼠标划过视图界面的时候,界面上的部分区域会被替换成鼠标形状;待鼠标移出后,又恢复成自身模样。另一个例子还包括用户选中的一段文字需要突出显示。但是,这些效果只是临时的,而不能算作视图状态。但是交互只是在屏幕上进行的,如果图像的显示设备不是屏幕,而是打印机之类的设备,那么就不应该显示这些临时效果。下面的代码可以判断显示设备是否在屏幕:
if (![NSGraphicsContext currentContext] isDrawingToScreen]) {
...
}
当使用drawRect:
以外的方式来绘制视图时,要先lockFocus
;待绘制结束的时候再unlockFocus
。foucs是一种独占性资源,一个视图占有了其他视图就不能使用,所以Cocoa采用了一种栈机制,将临时失去focus的视图记录再栈中,待到可以获取到focus的时候再退栈。
在多线程情况下,当一个线程在视图上调用lockFocus
以后,另外一个线程再在这个视图上调用lockFocus
可能会阻塞。此外,调用lockFocus
和unlockFocus
的可以不用是同一个线程。
优化视图绘制
视图绘制是一个高资源消耗的操作,常常需要优化。开发工具中带有如Sampler和Quartz Debug这类的工具可以帮你剖析性能瓶颈,更多信息可以参考Performance Overview。
Cocoa窗口能够管理一定数量的视图(100个)而不会有明显的性能问题,但是也不能因为滥用视图。有些情况下,用一个自定义视图管理很多细碎绘制,比对每个细碎绘制创建一个自定义视图来要的经济。
处理透明的视图会比处理不透明的视图更耗费资源,因为透明的视图需要考虑不同视图间的关系。所以有可能的话,将视图设置为不透明的,也就是在自定义视图中将isOpaque
方法的返回值设置成YES(默认是NO)。需要注意的是,这样一来该自定义视图就必须用不同非透明的颜色来填充此视图。
Cocoa提供了两种绘制视图的API接口。一种是像display
, displayRect:
这种即时的直接绘制接口;另外一种是setNeedsDisplay:
、setNeedsDisplayInRect:
这种延时的间接接口。过度使用前者会带来大量性能问题。所以建议是多使用后者。
drawRect:
方法的输入参数是一个矩形(由所有需要重新绘制的区域合并而成的矩形)。虽然drawRect:
可以在超出这个矩形的范围绘制,但是最终超出参数所指定的矩形部分会被切除,也就造成了浪费。所以尽量避免溢出性绘制。但更聪明的实现会从getRectsBeingDrawn:count:
和 needsToDrawRect:
中获取具体的失效的区域,而只重新绘制这些区域,从而提供性能。getRectsBeingDrawn:count:
所返回的是一组不重叠的矩形区域(drawRect:
的输入参数也就是由这些区域合并而成的)。needsToDrawRect
是getRectsBeingDrawn:count:
的方便版本,用于判断一个区域是否要绘制。
前面提到,如果drawRect:
绘制的图像超出了传入参数的矩形区域,则会被剪除。但剪除也是需要耗费时间的,如果你能保证自定义视图的drawRect:
不会溢出性绘制的话,可以通过改写wantsDefaultClipping
,让其返回NO来避免默认的剪除行为。关闭默认剪除,却又不能保证drawRect:
在限定区域剪除,会导致溢出的部分污染其他视图。
调整窗口大小的过程会不断重绘窗口,这对自定义视图的绘制实现是一个考验,如果实现不好,容易出现卡顿。有几种策略可以帮你在窗口大小调整的时候让用户获得合适感受:
- 牺牲细节,换取速度。NSView的
inLiveResize
会让你知道正处于窗口大小调整过程中,让你可以此过程中的绘制和正常情况下的绘制区别开。 - 只绘制展现的部分。OS X 10.4以后可以用
getRectsExposedDuringLiveResize:count:
来获悉哪个视图是展现出来的。OS X 10.3版本则需要使用getRectsBeingDrawn:count:
- NSView还有
viewWillStartLiveResize
和viewDidEndLiveResize
两个方法来通知窗口大小调整的开始和结束。在开始阶段可以缓冲一些数据,来加快绘制;结束的时候再把这些数据清掉。有一点需要注意的是,记得在这两个方法里面合适的地方调用super
方法,这样自定义视图的子视图也能获得通知。 - 在调整窗口大小的时候,自定义视图还可以通过
preservesContentDuringLiveResize
返回YES,来表明自己可以保留部分视图图像。然后自定义视图可以通过setFrameSize:
来无效化那些在窗口大小调整过程中必须重绘的部分。rectPreservedDuringLiveResize
告知哪些矩形区域不需要重绘,getRectsExposedDuringLiveResize:count:
则返回需要调整的矩形区域,然后传给setNeedsDisplayInRect:
。
其他参考:
- Cocoa Event Handling Guide
- Drag and Drop Programming Topics
- Printing Programming Guide for Mac
- Cocoa Drawing Guide
(完)