協議與委託代理回調在以前的博客中也是常常提到和用到的在《Objective-C中的委託(代理)模式》和《iOS開發之窺探UICollectionViewController(四) --一款功能強大的自定義瀑布流》等博客內容中都用到的Delegate回調。說到協議,在Objective-C中也是有協議的,而且Swift中的協議和Objc中的協議使用起來也是大同小異的,在Java等現代面向對象編程語言中有接口(Interface)的概念,其實和Swift中或者Objc中的Protocol(協議)是一個東西。論Interface和Protocol的功能來講,二者也是大同小異的。html
今天就結合兩個實例來窺探一下Swift中的協議與Delegate回調(委託代理回調)。本篇先給出CocoaTouch中經常使用控件UITableView的經常使用回調,並以此來認識一下回調的使用方式。緊接着會給出如何去實現本身的Delegate回調,即在自定義控件中去實現委託代理回調。言歸正傳,開始今天的博客主題。git
一.從UITableView中來窺探協議的委託代理回調github
UITableView這個高級控件在iOS開發中的出鏡率是比較高的,今天的重點不是介紹如何使用UITableView, 而是讓經過UITableView的工做方式來直觀的感覺一下協議的使用場景,以及Delegate代理的工做方式。若是你對UITableView控件不熟的話,徹底能夠跳過這一部分,直接進入第二部分。若是你要更好的理解Delegate委託回調,仍是頗有必要看這一部分的。編程
下面就先以UITableView的UITableViewDatasource協議來看一下委託代理的使用方式。爲了簡化代碼呢,下面的TableView的使用就沒有實現UITableViewDelegate協議仍是那句話,今天的重點是Protocol和Delegate, 而不是如何使用UITableView。下方的截圖就是咱們要使用UITableView和UITableViewDatasource來作的事情。固然下方的實例不管是代碼仍是佈局方面仍是灰常簡單的,運行效果以下所示。swift
上面的Cell中就是一個ImageView和一個Label, 佈局灰常簡單啦,接下來就簡單介紹一下在Swift中是如何實現(說白了,和Objc實現起來大同小異)。仍是結合着Storyboard來作吧,畢竟使用Storyboard佈局更爲簡單一些。數組
1. 使用Storyboard來佈局控件,控件佈局以下:網絡
2. 給上述Cell綁定相應的Swift源碼,並關聯ImageView和Label, 相應Cell(BeautifulGrillCell)的代碼以下所示。girlImageView即爲作吧的圖片,閉包
girlNameLable爲圖片右邊的文字。app
1 import UIKit 2 3 class BeautifulGrillCell: UITableViewCell { 4 5 @IBOutlet var girlImageView: UIImageView! 6 7 @IBOutlet var girlNameLable: UILabel! 8 9 override func awakeFromNib() { 10 super.awakeFromNib() 11 // Initialization code 12 } 13 14 override func setSelected(selected: Bool, animated: Bool) { 15 super.setSelected(selected, animated: animated) 16 17 // Configure the view for the selected state 18 } 19 20 }
3.接下來就是要模擬咱們在TableView上顯示的數據了,在正常開放中這些數據每每來源於網絡請求,而在本篇博客中就模擬數據源,來爲咱們的TableView提供顯示的數據。數據源的格式是一個數組,而數組中存放的是多個字典,每一個字典有兩個鍵值對,一個鍵值對存儲要顯示圖片的文件名,另外一個鍵值對則存儲美女的名字。爲了使該數據的存儲結構,請看下方結構圖。編程語言
原理圖有了,接下來就要使用代碼來建立出上述結構的數據以供TableView的數據源使用,下面的方法就是實現上述結構的函數。
(1) 首先咱們要在視圖控制器相應的類中添加一個可變數組,用來存放數據,以下所示:
1 private var dataSource:Array<Dictionary<String, String>>?
(2) 接着就是往上面這個數組中填充數據了,代碼以下:
1 //-----------建立Table要顯示的數據------------------------- 2 func createSourceData() { 3 self.dataSource = Array<Dictionary<String, String>>(); 4 for (var i = 0; i<10; i++) { 5 let imageName:String = "00\(i).jpg" 6 let girlName:String = "美女\(i + 1)" 7 self.dataSource?.append([IMAGE_NAME:imageName, GIRL_NAME:girlName]) 8 } 9 }
4. 咱們上面Storyboard中的視圖控制器使用的是UIViewController而不是UITableViewController。 咱們在UIViewController上貼了一層UITableView, 因此咱們須要在相應的ViewController對應的Swift源碼中進行UITableView的綁定,並實現UITableViewDatasource代理,併爲UITableView指定該代理。下方的代碼就是關聯tableview並指定代理方法。代碼以下:
1 import UIKit 2 3 class ViewController: UIViewController, UITableViewDataSource { 4 5 @IBOutlet var myTableView: UITableView! 6 //life cycle 7 override func viewDidLoad() { 8 super.viewDidLoad() 9 self.createSourceData() 10 self.myTableView.dataSource = self 11 } 12 }
4. 對myTableView的dataSource(數據提供者)指定完代理對象後,接下來就是要實現UITableViewDataSource中的相應的方法了,ViewController經過這些協議委託回調的代理方法來爲TableView提供數據。下方是UITableViewDataSource委託方法中返回TableView的Section個數的回調方法,以下所示:
1 /** 2 - parameter tableView: 當前要顯示的TableView 3 4 - returns: TableView中Section的個數 5 */ 6 func numberOfSectionsInTableView(tableView: UITableView) -> Int { 7 return 18 }
5.上面回調方法是返回Section個數的,緊接着下方就是返回每一個Section中Cell個數的回調方法。Cell的個數就是數組dataSource中元素的個數。
1 /** 2 返回每一個Section中的Cell個數 3 4 - parameter tableView: 當前顯示的TableView 5 - parameter section: 對應的Section 6 7 - returns: 對應Section中cell的個數 8 */ 9 func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ 10 return self.dataSource!.count
11 }
6. 下面這個方法是比較重要的,下方的方法,就是返回每行的Cell的委託回調方法。經過Cell的重用標示符來建立Cell的實例對象,並對Cell上的一些屬性賦值,並返回當前是Cell實例對象,代碼以下所示。
1 /** 2 返回要顯示的Cell 3 4 - parameter tableView: cell要顯示的TableView 5 - parameter indexPath: cell的索引信息 6 7 - returns: 返回要顯示的Cell對象 8 */ 9 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 10 11 let cell:BeautifulGrillCell = self.myTableView.dequeueReusableCellWithIdentifier("BeautifulGrillCell", forIndexPath: indexPath) as! BeautifulGrillCell 12 13 let tempItem:Dictionary? = self.dataSource![indexPath.row] 14 15 if tempItem != nil { 16 let imageName:String = tempItem![IMAGE_NAME]! 17 cell.girlImageView.image = UIImage(named: imageName) 18 19 let girlName:String = tempItem![GIRL_NAME]! 20 cell.girlNameLable.text = girlName 21 } 22 23 return cell 24 } 25 }
通過上面這些步驟,你就能夠去實現博客最上方截圖中的效果了,上面主要用到的仍是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 protocol EditViewControllerDelegate: NSObjectProtocol{ 2 func fetchGirlName(name:String) 3 }
(3) 接着要實現EditViewController類中的東西了,代碼以下。
成員變量var girlOldName:String?負責接收上個頁面傳過來的美女的姓名。weak var delegate: EditViewControllerDelegate? 這個聲明爲weak的delegate成員變量則是必需要實現EditViewControllerDelegate協議的委託代理者,使用weak修飾爲了不強引用循環。接着是girlNameTextField就是關聯的輸入框了,負責接收用戶輸入,把值交付給委託代理者。
在viewWillDisappear方法中,會將用戶輸入的值交付給委託代理者的fetchGirlName方法。deinit是析構函數,用來觀察是否引發強引用循環,由於咱們是使用的weak, 因此不會引發強引用循環,該deinit方法當返回時,是會被釋放掉的。
1 class EditViewController: UIViewController { 2 3 var girlOldName:String? 4 weak var delegate: EditViewControllerDelegate? 5 @IBOutlet var girlNameTextField: UITextField! 6 7 8 override func viewDidLoad() { 9 super.viewDidLoad() 10 if self.girlOldName != nil { 11 self.girlNameTextField.text = self.girlOldName! 12 } 13 } 14 15 override func viewWillDisappear(animated: Bool) { 16 let name:String! = self.girlNameTextField.text 17 if name != "" { 18 if delegate != nil { 19 delegate!.fetchGirlName(name) 20 } 21 } 22 } 23 24 override func didReceiveMemoryWarning() { 25 super.didReceiveMemoryWarning() 26 } 27 28 deinit { 29 print("釋放") 30 } 31 }
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 //-----------UITableViewDelegate------------------ 2 func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 3 4 //記錄當前點擊的IndexPath 5 self.selectIndexPath = indexPath 6 7 //獲取當前點擊的Cell對象 8 let currentSelectCell:BeautifulGrillCell? = self.myTableView.cellForRowAtIndexPath(indexPath) as? BeautifulGrillCell 9 10 //從storyboard中實例化編輯視圖控制器 11 let editViewController:EditViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("EditViewController") as! EditViewController 12 13 //指定編輯視圖控制器委託代理對象 14 editViewController.delegate = self 15 16 //把點擊Cell上的值傳遞給編輯視圖控制器 17 if currentSelectCell != nil { 18 editViewController.girlOldName = currentSelectCell!.girlNameLable.text! 19 } 20 21 //push到編輯視圖控制器 22 self.navigationController?.pushViewController(editViewController, animated: true) 23 }
(3)上面是跳轉,接下來就是要實現EditViewControllerDelegate中的回調方法,來處理相應的回調參數了。下方就是在表視圖中實現的回調方法,具體請看代碼中的註釋:
1 //-----------EditViewControllerDelegate------------------ 2 3 func fetchGirlName(name: String) { 4 5 if selectIndexPath != nil { 6 //獲取當前點擊Cell的索引 7 let index = (selectIndexPath?.row)! 8 9 //更新數據源中相應的數據 10 self.dataSource![index][GIRL_NAME] = name 11 12 //重載TableView 13 self.myTableView.reloadData() 14 } 15 16 }
通過上面的步驟,咱們就能夠去定義屬於本身的協議,並在此協議上實現委託回調了。上面的場景在iOS開發中極爲常見,使用場景也是比較普遍的。因此協議不管在Swift仍是在iOS開發中都是極爲重要的概念之一。好今天的博客內容也挺多的了,就到此爲止,剩下的東西,會在之後的博客中繼續更新。
上面實例GitHub分享地址(基於Xcode7.1):https://github.com/lizelu/SwiftDelegateDemo