協議與委託代理回撥在之前的部落格中也是經常提到和用到的在《Objective-C中的委託(代理)模式》和《iOS開發之窺探UICollectionViewController(四) –一款功能強大的自定義瀑布流》等部落格內容中都用到的Delegate回撥。說到協議,在Objective-C中也是有協議的,並且Swift中的協議和Objc中的協議使用起來也是大同小異的,在Java等現代物件導向程式語言中有介面(Interface)的概念,其實和Swift中或者Objc中的Protocol(協議)是一個東西。論Interface和Protocol的功能來說,兩者也是大同小異的。
今天就結合兩個例項來窺探一下Swift中的協議與Delegate回撥(委託代理回撥)。本篇先給出CocoaTouch中常用控制元件UITableView的常用回撥,並以此來認識一下回撥的使用方式。緊接著會給出如何去實現自己的Delegate回撥,即在自定義控制元件中去實現委託代理回撥。言歸正傳,開始今天的部落格主題。
一.從UITableView中來窺探協議的委託代理回撥
UITableView這個高階控制元件在iOS開發中的出鏡率是比較高的,今天的重點不是介紹如何使用UITableView, 而是讓通過UITableView的工作方式來直觀的感受一下協議的使用場景,以及Delegate代理的工作方式。如果你對UITableView控制元件不熟的話,完全可以跳過這一部分,直接進入第二部分。如果你要更好的理解Delegate委託回撥,還是很有必要看這一部分的。
下面就先以UITableView的UITableViewDatasource協議來看一下委託代理的使用方式。為了簡化程式碼呢,下面的TableView的使用就沒有實現UITableViewDelegate協議還是那句話,今天的重點是Protocol和Delegate, 而不是如何使用UITableView。下方的截圖就是我們要使用UITableView和UITableViewDatasource來做的事情。當然下方的例項無論是程式碼還是佈局方面還是灰常簡單的,執行效果如下所示。
上面的Cell中就是一個ImageView和一個Label, 佈局灰常簡單啦,接下來就簡單介紹一下在Swift中是如何實現(說白了,和Objc實現起來大同小異)。還是結合著Storyboard來做吧,畢竟使用Storyboard佈局更為簡單一些。
1. 使用Storyboard來佈局控制元件,控制元件佈局如下:
2. 給上述Cell繫結相應的Swift原始碼,並關聯ImageView和Label, 相應Cell(BeautifulGrillCell)的程式碼如下所示。girlImageView即為做吧的圖片,
girlNameLable為圖片右邊的文字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import UIKit class BeautifulGrillCell: UITableViewCell { @IBOutlet var girlImageView: UIImageView! @IBOutlet var girlNameLable: UILabel! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } |
3.接下來就是要模擬我們在TableView上顯示的資料了,在正常開放中這些資料往往來源於網路請求,而在本篇部落格中就模擬資料來源,來為我們的TableView提供顯示的資料。資料來源的格式是一個陣列,而陣列中存放的是多個字典,每個字典有兩個鍵值對,一個鍵值對儲存要顯示圖片的檔名,另一個鍵值對則儲存美女的名字。為了使該資料的儲存結構,請看下方結構圖。
原理圖有了,接下來就要使用程式碼來建立出上述結構的資料以供TableView的資料來源使用,下面的方法就是實現上述結構的函式。
(1) 首先我們要在檢視控制器相應的類中新增一個可變陣列,用來存放資料,如下所示:
1 |
private var dataSource:Array<Dictionary<String, String>>? |
(2) 接著就是往上面這個陣列中填充資料了,程式碼如下:
1 2 3 4 5 6 7 8 9 |
//-----------建立Table要顯示的資料------------------------- func createSourceData() { self.dataSource = Array<Dictionary<String, String>>(); for (var i = 0; i<10; i++) { let imageName:String = "00\(i).jpg" let girlName:String = "美女\(i + 1)" self.dataSource?.append([IMAGE_NAME:imageName, GIRL_NAME:girlName]) } } |
4. 我們上面Storyboard中的檢視控制器使用的是UIViewController而不是UITableViewController。 我們在UIViewController上貼了一層UITableView, 所以我們需要在相應的ViewController對應的Swift原始碼中進行UITableView的繫結,並實現UITableViewDatasource代理,併為UITableView指定該代理。下方的程式碼就是關聯tableview並指定代理方法。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
import UIKit class ViewController: UIViewController, UITableViewDataSource { @IBOutlet var myTableView: UITableView! //life cycle override func viewDidLoad() { super.viewDidLoad() self.createSourceData() self.myTableView.dataSource = self } } |
4. 對myTableView的dataSource(資料提供者)指定完代理物件後,接下來就是要實現UITableViewDataSource中的相應的方法了,ViewController通過這些協議委託回撥的代理方法來為TableView提供資料。下方是UITableViewDataSource委託方法中返回TableView的Section個數的回撥方法,如下所示:
1 2 3 4 5 6 7 |
/** - parameter tableView: 當前要顯示的TableView - returns: TableView中Section的個數 */ func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 18 } |
5.上面回撥方法是返回Section個數的,緊接著下方就是返回每個Section中Cell個數的回撥方法。Cell的個數就是陣列dataSource中元素的個數。
1 2 3 4 5 6 7 8 9 10 11 |
/** 返回每個Section中的Cell個數 - parameter tableView: 當前顯示的TableView - parameter section: 對應的Section - returns: 對應Section中cell的個數 */ func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return self.dataSource!.count } |
6. 下面這個方法是比較重要的,下方的方法,就是返回每行的Cell的委託回撥方法。通過Cell的重用標示符來建立Cell的例項物件,並對Cell上的一些屬性賦值,並返回當前是Cell例項物件,程式碼如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** 返回要顯示的Cell - parameter tableView: cell要顯示的TableView - parameter indexPath: cell的索引資訊 - returns: 返回要顯示的Cell物件 */ func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:BeautifulGrillCell = self.myTableView.dequeueReusableCellWithIdentifier("BeautifulGrillCell", forIndexPath: indexPath) as! BeautifulGrillCell let tempItem:Dictionary? = self.dataSource![indexPath.row] if tempItem != nil { let imageName:String = tempItem![IMAGE_NAME]! cell.girlImageView.image = UIImage(named: imageName) let girlName:String = tempItem![GIRL_NAME]! cell.girlNameLable.text = girlName } return cell } } |
經過上面這些步驟,你就可以去實現部落格最上方截圖中的效果了,上面主要用到的還是TableView的UITableViewDatasource委託代理, 使用方法如上。上面使用的委託回撥主要是使用Swift中的協議(Protocol)來實現的。那麼如何使用協議來實現你自己的委託回撥呢?這將是下面將要介紹的內容。
二. 認識協議,並使用協議實現委託回撥
接下來的內容就要介紹如何使用協議來定義屬於你自己的委託代理回撥(Delegate)了。第二部分還是以例項為準,在上面的Demo中加入我們自己定義的委託代理回撥。我們需要做的就是,在上面介面中,我們點選任意Cell就可以Push(導航控制器展示檢視控制器的一種方式,可以理解為檢視控制器壓棧的過程)到一個ViewController中,這個ViewController要做的事情就是輸入美女的名字,點選返回後通過自己定義的委託回撥,把你輸入的值回撥到上一個頁面(TableView)中去,並修改相應Cell上的名字。說白了,就是對美女的名字做一個修改。
如果上面的文字讓你迷惑的話,那麼接下來看例項好了,該例項還算是簡單的。下方是例項的操作步驟,如下所示:
上面例項的意思就是把下一個頁面的值通過委託代理回撥的形式傳到上個頁面中去,在前面的部落格《窺探Swift之函式與閉包的應用例項》中也做了同樣的事情,不過之前我們是使用閉包(Closure)回撥來實現的。先在我們要通過Delegate來實現。接下來我們就定義協議,然後再協議的基礎上實現委託代理回撥。接下來了開始我擴充的部分。
1.實現編輯美女姓名的頁面
(1) 在Storyboard上新新增一個檢視控制器(UIViewController), 並命名為EditViewController,給檢視控制器就是上方截圖中綠色的那個檢視控制器,主要用來對美女姓名 修改,並通過委託回撥把值傳給上個頁面。該檢視控制器的頁面佈局比較簡單,具體如下所示:
(2)UI就如數所示,為EditViewController關聯EditViewController.swift原始檔後,再對其上面的使用到的控制元件進行關聯即可。緊接著我們要實現一個協議,這個協議我們用來所委託回撥使用。這個協議可以定義在EditViewController.swift原始檔中。在協議定義之前,先對什麼是協議簡單的提上一嘴。先簡單的理解,協議中的方法只有宣告,沒有實現,並且使用protocol關鍵自進行宣告,下方的程式碼就是我們要使用的協議。協議中有一個fetchGirlName(name:String)的方法,用來回撥出輸入的數值。預設方法是必選的,你可以使用optional關鍵字使方法可選,在此就不做過多贅述了。
1 2 3 |
protocol EditViewControllerDelegate: NSObjectProtocol{ func fetchGirlName(name:String) } |
(3) 接著要實現EditViewController類中的東西了,程式碼如下。
成員變數var girlOldName:String?負責接收上個頁面傳過來的美女的姓名。weak var delegate: EditViewControllerDelegate? 這個宣告為weak的delegate成員變數則是必須要實現EditViewControllerDelegate協議的委託代理者,使用weak修飾為了避免強引用迴圈。接著是girlNameTextField就是關聯的輸入框了,負責接收使用者輸入,把值交付給委託代理者。
在viewWillDisappear方法中,會將使用者輸入的值交付給委託代理者的fetchGirlName方法。deinit是解構函式,用來觀察是否引起強引用迴圈,因為我們是使用的weak, 所以不會引起強引用迴圈,該deinit方法當返回時,是會被釋放掉的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class EditViewController: UIViewController { var girlOldName:String? weak var delegate: EditViewControllerDelegate? @IBOutlet var girlNameTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() if self.girlOldName != nil { self.girlNameTextField.text = self.girlOldName! } } override func viewWillDisappear(animated: Bool) { let name:String! = self.girlNameTextField.text if name != "" { if delegate != nil { delegate!.fetchGirlName(name) } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } deinit { print("釋放") } } |
2.上面的程式碼是實現編輯頁面並實現相應的委託協議,下方就是要從之前TableView中進行跳轉。也就是點選TableView的每一行,然後跳轉到編輯頁面對其當前點選的cell進行編輯,編輯後返回通過代理進行值的修改。
(1)首先要解決的就是點選Cell跳轉到EditViewController, 要執行這個事件,我們還必須實現TableView的另一個協議,就是UITableViewDelegate, 以為點選Cell的事件獲取的方法就在TableViewDelegate中。所以我們要在TableView所在的ViewController中的viewDidLoad()中指定UITableViewDelegate的委託代理者。如下所示。同時該ViewContoller也要實現UITableViewDelegate協議。
1 |
self.myTableView.delegate = self |
(2) 實現UITableViewDelegate協議中點選Cell的方法,方法中的內容如下所示。在該方法中,首先我們要暫存一下點選的是哪個Cell, 也就是記錄一下點選Cell的IndexPath, 然後就是獲取點選的Cell物件,因為通過該Cell物件,可以獲取相應Cell上的資料。具體的不多說了,請看程式碼中的註釋。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//-----------UITableViewDelegate------------------ func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { //記錄當前點選的IndexPath self.selectIndexPath = indexPath //獲取當前點選的Cell物件 let currentSelectCell:BeautifulGrillCell? = self.myTableView.cellForRowAtIndexPath(indexPath) as? BeautifulGrillCell //從storyboard中例項化編輯檢視控制器 let editViewController:EditViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("EditViewController") as! EditViewController //指定編輯檢視控制器委託代理物件 editViewController.delegate = self //把點選Cell上的值傳遞給編輯檢視控制器 if currentSelectCell != nil { editViewController.girlOldName = currentSelectCell!.girlNameLable.text! } //push到編輯檢視控制器 self.navigationController?.pushViewController(editViewController, animated: true) } |
(3)上面是跳轉,接下來就是要實現EditViewControllerDelegate中的回撥方法,來處理相應的回撥引數了。下方就是在表檢視中實現的回撥方法,具體請看程式碼中的註釋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//-----------EditViewControllerDelegate------------------ func fetchGirlName(name: String) { if selectIndexPath != nil { //獲取當前點選Cell的索引 let index = (selectIndexPath?.row)! //更新資料來源中相應的資料 self.dataSource![index][GIRL_NAME] = name //過載TableView self.myTableView.reloadData() } } |
經過上面的步驟,我們就可以去定義屬於自己的協議,並在此協議上實現委託回撥了。上面的場景在iOS開發中極為常見,使用場景也是比較廣泛的。所以協議無論在Swift還是在iOS開發中都是極為重要的概念之一。好今天的部落格內容也挺多的了,就到此為止,剩下的東西,會在以後的部落格中繼續更新。
上面例項GitHub分享地址(基於Xcode7.1):https://github.com/lizelu/SwiftDelegateDemo
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!