close

大家好,終於順利的進入第六個章節。

第六節課主要的內容是 Protocol 、 Delegation 和 Gestures 的介紹和示範。

下面附上 Youtube 的連結。


在上一堂課,大家應該都已經照著講師的示範,完成了一個簡單的笑臉程式了。

但我們所完成的只是 MVC 中的 View ,我們還需要建立一個 Model ,所以下面的課程有一大部分就是在完成這項工作。

不過事實上,上一次所完成的 faceView 還有一個小尾巴,那就是在 storyboard 上的 faceView 視圖依然是空白的。

這並不是說上面沒有東西,而只是系統沒有把它顯示出來而已,為了讓編寫者更直觀地知道當程式碼變化時,圖案的對應改變,我們必須加上一行程式碼在 faceView 的編碼中。

@IBDesignable    //  放在每個想要設置在右側欄位的變量宣告之前

這樣就可以在 storyboard 上看到對應的圖案。

另外我們還可以藉由輸入另一個程式碼,將我們想要的屬性參數設置為可以在 storyboard 右側的欄位中直接出現設置選項。

@IBInspectable

本例子是將變量屬性,Line Width 、 Color 和 Scale 設置為可由視窗選擇之屬性。

而當我們將三個屬性的值都更改過之後,同樣的更改也會同步出現在我們的程式碼中,不需要再重新寫一遍。


接下來,我們要把缺少的 Model 補上,於是我們在 HappinessViewController.swift 裡面將 model 寫入。

這個 model 建立一個變量屬性 happiness ,並設期初始值為 50 。

接著利用屬性觀察器限制其值位於 0 - 100 之間。

但這裡我們還沒有將這一個 model 和 view 連接,所以還要再寫一些東西。


為了後面的程式進展,我們必須瞭解 Extension 這個工具。

說白話一點, Extension 就是一個用來給現存的類、結構和枚舉增加屬性或方法的小語法。

要注意的是, Extension 並不支援修改或替代,它只能增加以前沒有的屬性,而且增加的屬性只能是運算屬性,不能是存儲屬性。


另外我們還需要瞭解什麼是 Protocol 和 Delegation 。

先講講 Protocol 吧,它是一個相對簡便地表達 API 的方法,它通常只是用於調用一個類中的其中一個方法,而不需要直接抓取整個類。

Protocol 雖然也是一個類型,且形式與類很相似,但它最大的不同就是沒有實現方法,只有聲明。

它提供了屬性和方法的聲明,然後丟給其他遵從它這個協議的對象去做實現。

那麼怎樣才能使用 Protocol 呢?必須滿足一些條件,投影片中剛好有敘述三個基本條件。

第三步,

1. the protocol declaration (what properties and methods are in the protocol)

2. the declaration where a class, struct or enum says that it implements a protocol 

3. the actual implementation of the protocol in said class, struct or enum 


之前的課程中提到過, View 是如何與 Controller 做溝通的,那就是利用 Protocol 。

那麼具體上要如何使用 Protocol 呢?

答案是依靠 Delegation 來實現 Protocol 。

怎麼做呢?

1. Create a delegation protocol (defines what the View wants the Controller to take care of) 

2. Create a delegate property in the View whose type is that delegation protocol

3. Use the delegate property in the View to get/do things it can’t own or control

4. Controller declares that it implements the protocol 

5. Controller sets self as the delegate of the View by setting the property in #2 above 6. Implement the protocol in the Controller 


讓我們用 Demo 來幫助理解這幾個步驟吧!

第一步,在 FaceView 的程式碼中建立一個協議 FaceViewDataSource ,它的用途是從其他代理那邊得到一個值並回傳給 FaceView 作為 smiliness 的值。

第二步,宣告一個代理屬性的變量。

這裡大家可能會注意到兩個讓人感到好奇的地方,為什麼最前面要加上 weak 和最後面為什麼要用問號變成 Optional ?

首先,使用 weak 的原因是為了避免造成強指向,那會導致這些東西的記憶體無法被釋放,增加系統負擔,所以先放一個 weak 可以告訴系統說不需要存取在記憶體中,用完就將它釋放。

至於為什麼要用 Optional 類型,那是因為我們不一定會有數值回傳,那麼當沒有值的時候,我們的笑臉就不能執行嗎?

這樣也蠻奇怪的。所以我們將它設定為 Optional ,當沒有值回傳進來時會預設為 Nil ,那麼至少可以顯示一張等同於 0 的臉。

第三步,將原本 smiliness 中的常數值換成我們剛剛建立的 dataSource 。

這邊有兩點大家看到的時候可能會產生疑惑。

第一點就是為什麼代理內部是填 self ? 這是因為原本的 smilinessForFaceView 內就是傳資料到 FaceView (sender: FaceView) ,而 smiliness 本來就是在 FaceView 內宣告,所以等於是 self 。

第二點就是說為什麼後面會接 "?? 0.0" ?這是因為在下面還宣告了一個 smilePath ,它需要導入 smiliness 進去,不能為 nil ,所以我們必須另外設一個狀態。

當 dataSource?.smilinessForFaceView(self) 不是 nil 的時候就回傳結果給 smilePath ,若為 nil 則回傳 0.0 (Double) 。

第四步,在 Controller 實現這個協議來協助 Model 與 View 之間的溝通。

可以看到我們在原本的 class HappinessViewController: UIViewController 後面又掛了一個 FaceViewDataSource 來宣告 HappinessViewController 會使用此協議。

接著調用此協議下的 smilinessForFaceView(sender: FaceView) -> Double? ,將 happiness 由 0 ~ 100 的設定轉換為 -1 ~ 1 的參數並回傳為 Double 類型。

這就是 Protocol 最標準的公用之一,幫助 Model 與 View 做溝通協調。

第五步,將 faceView 拉進 Controlle 作為 outlet 。

完成之後,我們的 Model 和 View 就順利連結起來了。

最後一步,讓我們的 View 在每次數據改變的時候重新繪製。


完成了 MVC 結構的笑臉程式,接著我們要導入手勢的動作。

藉由手指的滑動,拉縮等等動作來直覺化控制我們的笑臉,在 Swift 裡我們利用 UIGestureRecoginizer 來應用它的子類功能做手勢辨識。

手勢的使用主要有兩個步驟要做。

1. Adding a gesture recognizer to a UIView (asking the UIView to “recognize” that gesture)

增添一個手勢辨識器到 UIView ,讓 UIView 可以辨識手勢。

2. Providing a method to “handle” that gesture (not necessarily handled by the UIView)  

指定一個對象,當 UIView 理解這個手勢的動作,它需要告知誰?


那麼,如何在視圖中增加手勢辨識器呢?

通常會利用屬性監測器的 Set 方法來處理。

@IBOutlet weak var pannableView: UIView {

      didSet {

          let recognizer = UIPanGestureRecognizer(target: self, action: “pan:”)

          pannableView.addGestureRecognizer(recognizer) } 

至於其他細節請大家自己看投影片吧,我這邊只是大概挑出重點而已。

接下來開始將手勢的動作加入我們的笑臉程式裡。


首先我們想要將拉縮的手勢加入我們的笑臉,這樣我們就可以藉由兩根手指頭的縮放來調整圖案在螢幕中的大小尺寸。

做法是在 Controller 的 faceView outlet 內加入手勢控制的方法,接著在 View 裡面寫入具體的執行動作。

Controller :

View :

在 UIGestrueRecognizer 內有個子類叫做 addGestrueRecognizer ,它可用來將手勢辨識器加入程式之中,其文檔如下所示。

Declaration : func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer)

Description : Attaches a gesture recognizer to the view.

An object whose class descends from the UIGestureRecognizer class. This parameter must not be nil.

Parameters : gestureRecognizer An object whose class descends from the UIGestureRecognizer class. This parameter must not be nil.


除此之外,UIGestrueRecognizer 還有另一個子類叫做 UIPinchGestureRecognizer ,它用來表示手指的縮放動作,其文檔如下所示。

Declarationclass UIPinchGestureRecognizer : UIGestureRecognizer

Description : UIPinchGestureRecognizer is a concrete subclass of UIGestureRecognizer that looks for pinching gestures involving two touches. When the user moves the two fingers toward each other, the conventional meaning is zoom-out; when the user moves the two fingers away from each other, the conventional meaning is zoom-in.

Overview :

Pinching is a continuous gesture. The gesture begins (UIGestureRecognizerStateBegan) when the two touches have moved enough to be considered a pinch gesture. The gesture changes (UIGestureRecognizerStateChanged) when a finger moves (with both fingers remaining pressed). The gesture ends (UIGestureRecognizerStateEnded) when both fingers lift from the view.


faceView.addGestureRecognizer(UIPinchGestureRecognizer(target: faceView, action: "scale:")) 這段程式碼的意思是,在視圖 faceView 上建立一個手勢辨識器,並且調用縮放動作的辨識功能,將辨識到的資料回報給 faceView ,並且執行裡面的 scale 實例方法。

因此我們當然必須在 faceView 內宣告一個實例方法 scale 來執行動作。

 func scale(gestrue: UIPinchGestureRecognizer) {

        if gestrue.state == .Changed {

            scale *= gestrue.scale

            gestrue.scale = 1  } }

如上面程式碼的敘述,宣告一個函數 scale 作為實例方法,設置一個參數 gestrue 為 UIPinchGestrueRecognizer 類型,這樣 gesture 就可以接受到由在視圖上的手勢動作回傳的資料。

而實際動作是當手勢的狀態改變的時候,將 scale 乘上手勢的縮放比例來替代原本的 scale 數值,並將手勢的縮放比例還原為 1 。

這裡的 ".state" 與 ".Changed" 都是 UIGestrueRecognizer 的實例方法。

state 的參考文檔如下。

Declarationvar state: UIGestureRecognizerState { get }

Description : The current state of the gesture recognizer. (read-only)

The possible states a gesture recognizer can be in are represented by the constants of type UIGestureRecognizerState. Some of these states are not applicable to discrete gestures. 

The read-only version of the state property is intended for clients of a gesture-recognizer class and not subclasses.

Changed 的參考文檔如下。

Declarationcase Changed

Description : The gesture recognizer has received touches recognized as a change to a continuous gesture. It sends its action message (or messages) at the next cycle of the run loop.

The gesture recognizer has received touches recognized as a change to a continuous gesture. It sends its action message (or messages) at the next cycle of the run loop.


接下來我們再加入一個手勢動作,藉由手指上下滑動可以改變快樂的程度。

這段程式碼其實很簡單,只是有些新東西大家可能不知道作用,我們先看一下文檔內容吧。

translationInView 的文檔內容如下。

Declarationfunc translationInView(view: UIView?) -> CGPoint

Description : The translation of the pan gesture in the coordinate system of the specified view.

The view in whose coordinate system the translation of the pan gesture should be computed. If you want to adjust a view's location to keep it under the user's finger, request the translation in that view's superview's coordinate system.

Parameters: 

view The view in whose coordinate system the translation of the pan gesture should be computed. If you want to adjust a view's location to keep it under the user's finger, request the translation in that view's superview's coordinate system.

Returns : A point identifying the new location of a view in the coordinate system of its designated superview.


setTranslation 的文檔內容如下。

Declarationfunc setTranslation(translation: CGPoint, inView view: UIView?)

Description : Sets the translation value in the coordinate system of the specified view.

A point that identifies the new translation value.

Parameters : 

translation : A point that identifies the new translation value.

view : A view in whose coordinate system the translation is to occur.


CGPointZero 的文檔內容如下。

Declarationvar CGPointZero: CGPoint { get }

Description : A point constant with location (0,0). The zero point is equivalent to CGPointMake(0,0).

A point constant with location (0,0). The zero point is equivalent to CGPointMake(0,0).


看完文檔之後就會發現這段敘述其實相當單純。

gesture 有兩個狀態需要被考慮,那就是 Changed 和 Ended ,所以這裡用 switch 來控制這兩者的條件動作,無論 Changed 或是 Ended 都執行 Changed 的動作。

當手勢偵測到變化時,將最後改變的位置轉換為座標形式回傳給 translation 。

接著宣告一個調整值 happinessChange 來調整笑臉的開心程度,當這個調整值不為 0 的時候,將 happiness 更新為調整後的值,並將手勢的位置座標還原為 (0,0) 。

這樣一來,我們就可以藉由手指的上下滑動改變開心程度了。


下一堂課會著重討論複數 MVC 的架設,第六堂課就到此結束了。

By 宅宅阿軒

arrow
arrow
    文章標籤
    iOS8 Swift
    全站熱搜

    Cloud 發表在 痞客邦 留言(0) 人氣()