讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 10.1 子類化 >

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子類在應用啟動時進行實例化以瞭解詳情。)