首页 » iOS编程基础:Swift、Xcode和Cocoa入门指南 » iOS编程基础:Swift、Xcode和Cocoa入门指南全文在线阅读

《iOS编程基础:Swift、Xcode和Cocoa入门指南》10.1 子类化

关灯直达底部

Cocoa提供了大量的对象,这些对象知道如何以你所期望的方式运作。比如,UIButton知道如何绘制自身,当用户轻拍时该如何响应;UITextField知道如何显示可编辑的文本,如何弹出键盘,以及如何接收键盘输入。

通常,Cocoa所提供的对象的默认行为与外观可能并不符合你的要求,你需要对其进行定制。不过,这并不表示你需要子类化!Cocoa类提供了很多方法供你调用,提供了很多属性供你设置,这都是用于自定义实例的,你首先应该使用它们。你应该查阅Cocoa类的文档来了解实例是否已经满足了你的要求。比如,UIButton的类文档表明你可以设置按钮的文本、文本颜色、内部图片、背景图片,以及其他很多特性与行为,无须子类化!

然而,有时设置属性和调用方法并不足以按照你所期望的方式来定制实例。在这种情况下,Cocoa提供了一些内部方法,这些方法在实例完成某些事情时会得到调用,你可以通过子类化和重写(参见第4章)来定制其行为。你没法获得任何Cocoa内建类的源代码,但依然可以对其进行子类化,创建新的类,除了你所进行的修改,其行为非常类似于内建类。

某些Cocoa Touch类总是需要子类化。比如,没有子类化的单纯的UIViewController是非常少见的,没有UIViewController子类的iOS应用基本上是不可用的。

另一个例子就是UIView。Cocoa Touch有很多内建的UIView子类,它们会按照需要运作并绘制自身(如UIButton、UITextField等),你很少需要对其进行子类化。另一方面,你可能会创建自己的UIView子类,其作用是以全新方式绘制自身。实际上绘制的并非UIView;在UIView需要绘制时,其drawRect:方法会得到调用,这样视图就可以绘制自身了。因此,以完全自定义的方式绘制UIView就需要子类化UIView并在子类中实现drawRect:。正如其文档中所述“绘制视图内容的子类应该重写该方法并实现自己的绘制代码”。文档说你需要子类化UIView,这样才能完全以自己的方式绘制内容。

比如,假设我们想让窗口包含一个水平线。Cocoa中并没有提供水平线接口部件,这样我们就需要创建自己的了,即将自身绘制为一条水平线的UIView。现在就来试一下:

1.在Empty Window示例项目中,选择File→New→File并指定iOS→Source→Cocoa Touch Class,让其成为UIView的子类,将其命名为MyHorizLine。Xcode会创建MyHorizLine.swift。请确保它是应用目标的一部分。

2.在MyHorizLine.swift中,将类声明的内容替换为如以下代码(不再解释了):


required init?(coder aDecoder: NSCoder) {    super.init(coder:aDecoder)    self.backgroundColor = UIColor.clearColor}override func drawRect(rect: CGRect) {    let c = UIGraphicsGetCurrentContext!    CGContextMoveToPoint(c, 0, 0)    CGContextAddLineToPoint(c, self.bounds.size.width, 0)    CGContextStrokePath(c)}  

3.编辑故事板。在对象库中找到UIView(叫作“View”),然后将其拖曳到画布的View对象上。你可以将其高度压缩一些。

4.选中刚才拖曳到画布上的UIView,使用身份查看器将其类修改为MyHorizLine。

构建并在模拟器中运行应用。你会看到一条水平线出现在nib中MyHorizLine实例顶部的位置处。该视图将自身绘制成了一条水平线,因为我们对其子类化想要这么做。

在该示例中,我们从一个没有绘制功能的UIView开始。这正是我们无须调用super的原因所在;UIView中drawRect:的默认实现什么都不做。但你还可以子类化内建的UIView子类以修改其绘制自身的方式。比如,UILabel的文档说该类中有两个方法用于实现该目的。drawTextInRect:与textRectForBounds:limitedToNumberOfLines:都显式表明:“该方法只应该由想要修改标签绘制方式的子类所重写。”这表明这些方法会在标签绘制自身时被Cocoa自动调用;这样,我们就可以子类化UILabel并在我们的子类中实现这些方法来修改特定类型的标签绘制自身的方式。

下面这个示例来自于我自己的应用,我子类化了UILabel,创建了一个标签,它通过重写drawTextInRect:来绘制自己的矩形边框并使其内容从边框中嵌入。文档中说道:“在重写的方法中,你可以进一步配置当前上下文,然后调用super完成实际的文本绘制工作。”下面来试一下:

1.在Empty Window项目中,创建一个新的类文件,它是UILabel的子类;将该类命名为MyBoundedLabel。

2.在MyBoundedLabel.swift中,将如下代码插入类声明体中:


override func drawTextInRect(rect: CGRect) {    let context = UIGraphicsGetCurrentContext!    CGContextStrokeRect(context, CGRectInset(self.bounds, 1.0, 1.0))    super.drawTextInRect(CGRectInset(rect, 5.0, 5.0))} 

3.编辑故事板,向界面添加一个UILabel,在身份查看器中将其类修改为MyBoundedLabel。

构建并运行应用,你会看到矩形是如何绘制的以及标签的文本是如何插入其中的。

说来也奇怪(如果使用过其他面向对象应用框架,那么你就会感到奇怪),在你的代码与Cocoa的交互过程中,子类化却是使用较少的一种方式。知道或是确定何时该子类化有点棘手,但通常的原则是如果没有要求,那么你就不应该使用子类化。大多数内建的Cocoa Touch类都不需要子类化(一些类的文档明确表示不允许子类化)。

子类化在Cocoa中很少见的一个原因是很多内建类都将委托(参见第11章)作为定制实例行为的首选方式。比如,你不会子类化UIApplication(单例共享应用实例的类)仅仅为了在应用加载完毕后做出响应,因为委托机制提供了解决方案(application:didFinishLaunchingWithOptions:)。这正是模板会创建AppDelegate类的原因所在,它不是UIApplication的子类,而是使用了UIApplicationDelegate协议。

另外,如果需要对应用基础的事件处理行为进行某些比较复杂的定制化工作,那么你可以子类化UIApplication来重写sendEvent:。文档专门有一节“Subclassing Notes”用来告诉你,这么做“非常少见”。(参见第6章关于如何确保UIApplication子类在应用启动时进行实例化以了解详情。)