本文是Apple Documentation Archive View Programming Guide的学习笔记。

自定义视图的高级主题

Advanced Custom View Tasks

前一章节讨论自定义视图中遇到的一般性话题,这一章节讨论个别话题。

视图所绘制的图像,常常因为用户交互过程中产生的行为,而需要暂时性修改。比如鼠标划过视图界面的时候,界面上的部分区域会被替换成鼠标形状;待鼠标移出后,又恢复成自身模样。另一个例子还包括用户选中的一段文字需要突出显示。但是,这些效果只是临时的,而不能算作视图状态。但是交互只是在屏幕上进行的,如果图像的显示设备不是屏幕,而是打印机之类的设备,那么就不应该显示这些临时效果。下面的代码可以判断显示设备是否在屏幕:

if (![NSGraphicsContext currentContext] isDrawingToScreen]) {
    ...
}

当使用drawRect:以外的方式来绘制视图时,要先lockFocus;待绘制结束的时候再unlockFocus。foucs是一种独占性资源,一个视图占有了其他视图就不能使用,所以Cocoa采用了一种栈机制,将临时失去focus的视图记录再栈中,待到可以获取到focus的时候再退栈。

在多线程情况下,当一个线程在视图上调用lockFocus以后,另外一个线程再在这个视图上调用lockFocus可能会阻塞。此外,调用lockFocusunlockFocus的可以不用是同一个线程。

优化视图绘制

Optimizing View Drawing

视图绘制是一个高资源消耗的操作,常常需要优化。开发工具中带有如Sampler和Quartz Debug这类的工具可以帮你剖析性能瓶颈,更多信息可以参考Performance Overview

Cocoa窗口能够管理一定数量的视图(100个)而不会有明显的性能问题,但是也不能因为滥用视图。有些情况下,用一个自定义视图管理很多细碎绘制,比对每个细碎绘制创建一个自定义视图来要的经济。

处理透明的视图会比处理不透明的视图更耗费资源,因为透明的视图需要考虑不同视图间的关系。所以有可能的话,将视图设置为不透明的,也就是在自定义视图中将isOpaque方法的返回值设置成YES(默认是NO)。需要注意的是,这样一来该自定义视图就必须用不同非透明的颜色来填充此视图。

Cocoa提供了两种绘制视图的API接口。一种是像display, displayRect:这种即时的直接绘制接口;另外一种是setNeedsDisplay:setNeedsDisplayInRect:这种延时的间接接口。过度使用前者会带来大量性能问题。所以建议是多使用后者。

drawRect:方法的输入参数是一个矩形(由所有需要重新绘制的区域合并而成的矩形)。虽然drawRect:可以在超出这个矩形的范围绘制,但是最终超出参数所指定的矩形部分会被切除,也就造成了浪费。所以尽量避免溢出性绘制。但更聪明的实现会从getRectsBeingDrawn:count: needsToDrawRect:中获取具体的失效的区域,而只重新绘制这些区域,从而提供性能。getRectsBeingDrawn:count:所返回的是一组不重叠的矩形区域(drawRect:的输入参数也就是由这些区域合并而成的)。needsToDrawRectgetRectsBeingDrawn:count:的方便版本,用于判断一个区域是否要绘制。

前面提到,如果drawRect:绘制的图像超出了传入参数的矩形区域,则会被剪除。但剪除也是需要耗费时间的,如果你能保证自定义视图的drawRect:不会溢出性绘制的话,可以通过改写wantsDefaultClipping ,让其返回NO来避免默认的剪除行为。关闭默认剪除,却又不能保证drawRect:在限定区域剪除,会导致溢出的部分污染其他视图。

调整窗口大小的过程会不断重绘窗口,这对自定义视图的绘制实现是一个考验,如果实现不好,容易出现卡顿。有几种策略可以帮你在窗口大小调整的时候让用户获得合适感受:

  • 牺牲细节,换取速度。NSView的inLiveResize会让你知道正处于窗口大小调整过程中,让你可以此过程中的绘制和正常情况下的绘制区别开。
  • 只绘制展现的部分。OS X 10.4以后可以用getRectsExposedDuringLiveResize:count:来获悉哪个视图是展现出来的。OS X 10.3版本则需要使用getRectsBeingDrawn:count:
  • NSView还有viewWillStartLiveResizeviewDidEndLiveResize两个方法来通知窗口大小调整的开始和结束。在开始阶段可以缓冲一些数据,来加快绘制;结束的时候再把这些数据清掉。有一点需要注意的是,记得在这两个方法里面合适的地方调用super方法,这样自定义视图的子视图也能获得通知。
  • 在调整窗口大小的时候,自定义视图还可以通过preservesContentDuringLiveResize 返回YES,来表明自己可以保留部分视图图像。然后自定义视图可以通过setFrameSize:来无效化那些在窗口大小调整过程中必须重绘的部分。rectPreservedDuringLiveResize告知哪些矩形区域不需要重绘, getRectsExposedDuringLiveResize:count:则返回需要调整的矩形区域,然后传给setNeedsDisplayInRect:

其他参考:

(完)