協(xié)議與委托代理回調(diào)在之前的博客中也是經(jīng)常提到和用到的在《Objective-C中的委托(代理)模式》和《iOS開發(fā)之窺探UICollectionViewController(四) --一款功能強(qiáng)大的自定義瀑布流》等博客內(nèi)容中都用到的Delegate回調(diào)。說到協(xié)議,在Objective-C中也是有協(xié)議的隙疚,并且Swift中的協(xié)議和Objc中的協(xié)議使用起來也是大同小異的,在Java等現(xiàn)代面向?qū)ο缶幊陶Z言中有接口(Interface)的概念污尉,其實(shí)和Swift中或者Objc中的Protocol(協(xié)議)是一個(gè)東西膀哲。論Interface和Protocol的功能來說,兩者也是大同小異的被碗。
今天就結(jié)合兩個(gè)實(shí)例來窺探一下Swift中的協(xié)議與Delegate回調(diào)(委托代理回調(diào))某宪。本篇先給出CocoaTouch中常用控件UITableView的常用回調(diào),并以此來認(rèn)識一下回調(diào)的使用方式锐朴。緊接著會給出如何去實(shí)現(xiàn)自己的Delegate回調(diào)兴喂,即在自定義控件中去實(shí)現(xiàn)委托代理回調(diào)。言歸正傳焚志,開始今天的博客主題衣迷。
一.從UITableView中來窺探協(xié)議的委托代理回調(diào)
UITableView這個(gè)高級控件在iOS開發(fā)中的出鏡率是比較高的,今天的重點(diǎn)不是介紹如何使用UITableView, 而是讓通過UITableView的工作方式來直觀的感受一下協(xié)議的使用場景酱酬,以及Delegate代理的工作方式壶谒。如果你對UITableView控件不熟的話,完全可以跳過這一部分膳沽,直接進(jìn)入第二部分汗菜。如果你要更好的理解Delegate委托回調(diào),還是很有必要看這一部分的挑社。
下面就先以UITableView的UITableViewDatasource協(xié)議來看一下委托代理的使用方式陨界。為了簡化代碼呢,下面的TableView的使用就沒有實(shí)現(xiàn)UITableViewDelegate協(xié)議還是那句話痛阻,今天的重點(diǎn)是Protocol和Delegate, 而不是如何使用UITableView普碎。下方的截圖就是我們要使用UITableView和UITableViewDatasource來做的事情。當(dāng)然下方的實(shí)例無論是代碼還是布局方面還是灰常簡單的录平,運(yùn)行效果如下所示。
上面的Cell中就是一個(gè)ImageView和一個(gè)Label, 布局灰常簡單啦缀皱,接下來就簡單介紹一下在Swift中是如何實(shí)現(xiàn)(說白了斗这,和Objc實(shí)現(xiàn)起來大同小異)。還是結(jié)合著Storyboard來做吧啤斗,畢竟使用Storyboard布局更為簡單一些表箭。
1. 使用Storyboard來布局控件,控件布局如下:
2. 給上述Cell綁定相應(yīng)的Swift源碼钮莲,并關(guān)聯(lián)ImageView和Label, 相應(yīng)Cell(BeautifulGrillCell)的代碼如下所示免钻。girlImageView即為做吧的圖片,
girlNameLable為圖片右邊的文字崔拥。
1import UIKit 2 3class BeautifulGrillCell: UITableViewCell { 4 5@IBOutletvargirlImageView: UIImageView! 6 7@IBOutletvargirlNameLable: UILabel! 8 9override func awakeFromNib() {10? ? ? ? super.awakeFromNib()11// Initialization code12? ? }1314override func setSelected(selected: Bool, animated: Bool) {15? ? ? ? super.setSelected(selected, animated: animated)1617// Configure the view for the selected state18? ? }1920}
3.接下來就是要模擬我們在TableView上顯示的數(shù)據(jù)了极舔,在正常開放中這些數(shù)據(jù)往往來源于網(wǎng)絡(luò)請求,而在本篇博客中就模擬數(shù)據(jù)源链瓦,來為我們的TableView提供顯示的數(shù)據(jù)拆魏。數(shù)據(jù)源的格式是一個(gè)數(shù)組盯桦,而數(shù)組中存放的是多個(gè)字典,每個(gè)字典有兩個(gè)鍵值對渤刃,一個(gè)鍵值對存儲要顯示圖片的文件名拥峦,另一個(gè)鍵值對則存儲美女的名字。為了使該數(shù)據(jù)的存儲結(jié)構(gòu)卖子,請看下方結(jié)構(gòu)圖略号。
原理圖有了,接下來就要使用代碼來創(chuàng)建出上述結(jié)構(gòu)的數(shù)據(jù)以供TableView的數(shù)據(jù)源使用洋闽,下面的方法就是實(shí)現(xiàn)上述結(jié)構(gòu)的函數(shù)玄柠。
? (1) 首先我們要在視圖控制器相應(yīng)的類中添加一個(gè)可變數(shù)組,用來存放數(shù)據(jù)喊递,如下所示:
1privatevardataSource:Array>?
(2) 接著就是往上面這個(gè)數(shù)組中填充數(shù)據(jù)了随闪,代碼如下:
1//-----------創(chuàng)建Table要顯示的數(shù)據(jù)-------------------------2? ? func createSourceData() {3self.dataSource = Array>();4for(vari =0; i<10; i++) {5let imageName:String ="00\(i).jpg"6let girlName:String ="美女\(i + 1)"7self.dataSource?.append([IMAGE_NAME:imageName, GIRL_NAME:girlName])8? ? ? ? }9}
4. 我們上面Storyboard中的視圖控制器使用的是UIViewController而不是UITableViewController。 我們在UIViewController上貼了一層UITableView, 所以我們需要在相應(yīng)的ViewController對應(yīng)的Swift源碼中進(jìn)行UITableView的綁定骚勘,并實(shí)現(xiàn)UITableViewDatasource代理铐伴,并為UITableView指定該代理。下方的代碼就是關(guān)聯(lián)tableview并指定代理方法俏讹。代碼如下:
1import UIKit 2 3class ViewController: UIViewController, UITableViewDataSource { 4 5@IBOutletvarmyTableView: UITableView! 6//life cycle 7override func viewDidLoad() { 8? ? ? ? super.viewDidLoad() 9? ? ? ? self.createSourceData()10self.myTableView.dataSource = self11? ? }12}
4. 對myTableView的dataSource(數(shù)據(jù)提供者)指定完代理對象后当宴,接下來就是要實(shí)現(xiàn)UITableViewDataSource中的相應(yīng)的方法了,ViewController通過這些協(xié)議委托回調(diào)的代理方法來為TableView提供數(shù)據(jù)泽疆。下方是UITableViewDataSource委托方法中返回TableView的Section個(gè)數(shù)的回調(diào)方法户矢,如下所示:
1/**2? ? - parameter tableView: 當(dāng)前要顯示的TableView34? ? - returns:? TableView中Section的個(gè)數(shù)5*/6func numberOfSectionsInTableView(tableView: UITableView) -> Int {7return18}
5.上面回調(diào)方法是返回Section個(gè)數(shù)的,緊接著下方就是返回每個(gè)Section中Cell個(gè)數(shù)的回調(diào)方法殉疼。Cell的個(gè)數(shù)就是數(shù)組dataSource中元素的個(gè)數(shù)梯浪。
1/** 2? ? 返回每個(gè)Section中的Cell個(gè)數(shù) 3 4? ? - parameter tableView: 當(dāng)前顯示的TableView 5? ? - parameter section:? 對應(yīng)的Section 6 7? ? - returns: 對應(yīng)Section中cell的個(gè)數(shù) 8*/ 9func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{10return self.dataSource!.count
11}
6. 下面這個(gè)方法是比較重要的,下方的方法瓢娜,就是返回每行的Cell的委托回調(diào)方法挂洛。通過Cell的重用標(biāo)示符來創(chuàng)建Cell的實(shí)例對象,并對Cell上的一些屬性賦值眠砾,并返回當(dāng)前是Cell實(shí)例對象虏劲,代碼如下所示。
1/** 2? ? 返回要顯示的Cell 3 4? ? - parameter tableView: cell要顯示的TableView 5? ? - parameter indexPath: cell的索引信息 6 7? ? - returns: 返回要顯示的Cell對象 8*/ 9func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {1011let cell:BeautifulGrillCell = self.myTableView.dequeueReusableCellWithIdentifier("BeautifulGrillCell", forIndexPath: indexPath)as! BeautifulGrillCell1213let tempItem:Dictionary? = self.dataSource![indexPath.row]1415iftempItem != nil {16let imageName:String = tempItem![IMAGE_NAME]!17cell.girlImageView.image = UIImage(named: imageName)1819let girlName:String = tempItem![GIRL_NAME]!20cell.girlNameLable.text = girlName21? ? ? ? }2223return cell24? ? }25}
經(jīng)過上面這些步驟褒颈,你就可以去實(shí)現(xiàn)博客最上方截圖中的效果了柒巫,上面主要用到的還是TableView的UITableViewDatasource委托代理, 使用方法如上。上面使用的委托回調(diào)主要是使用Swift中的協(xié)議(Protocol)來實(shí)現(xiàn)的谷丸。那么如何使用協(xié)議來實(shí)現(xiàn)你自己的委托回調(diào)呢堡掏?這將是下面將要介紹的內(nèi)容。
二. 認(rèn)識協(xié)議刨疼,并使用協(xié)議實(shí)現(xiàn)委托回調(diào)
接下來的內(nèi)容就要介紹如何使用協(xié)議來定義屬于你自己的委托代理回調(diào)(Delegate)了布疼。第二部分還是以實(shí)例為準(zhǔn)摊趾,在上面的Demo中加入我們自己定義的委托代理回調(diào)。我們需要做的就是游两,在上面界面中砾层,我們點(diǎn)擊任意Cell就可以Push(導(dǎo)航控制器展示視圖控制器的一種方式,可以理解為視圖控制器壓棧的過程)到一個(gè)ViewController中贱案,這個(gè)ViewController要做的事情就是輸入美女的名字肛炮,點(diǎn)擊返回后通過自己定義的委托回調(diào),把你輸入的值回調(diào)到上一個(gè)頁面(TableView)中去宝踪,并修改相應(yīng)Cell上的名字侨糟。說白了,就是對美女的名字做一個(gè)修改瘩燥。
如果上面的文字讓你迷惑的話秕重,那么接下來看實(shí)例好了,該實(shí)例還算是簡單的厉膀。下方是實(shí)例的操作步驟溶耘,如下所示:
上面實(shí)例的意思就是把下一個(gè)頁面的值通過委托代理回調(diào)的形式傳到上個(gè)頁面中去,在前面的博客《窺探Swift之函數(shù)與閉包的應(yīng)用實(shí)例》中也做了同樣的事情服鹅,不過之前我們是使用閉包(Closure)回調(diào)來實(shí)現(xiàn)的凳兵。先在我們要通過Delegate來實(shí)現(xiàn)。接下來我們就定義協(xié)議企软,然后再協(xié)議的基礎(chǔ)上實(shí)現(xiàn)委托代理回調(diào)庐扫。接下來了開始我擴(kuò)充的部分。
1.實(shí)現(xiàn)編輯美女姓名的頁面
(1) 在Storyboard上新添加一個(gè)視圖控制器(UIViewController), 并命名為EditViewController仗哨,給視圖控制器就是上方截圖中綠色的那個(gè)視圖控制器形庭,主要用來對美女姓名 修改,并通過委托回調(diào)把值傳給上個(gè)頁面厌漂。該視圖控制器的頁面布局比較簡單碘勉,具體如下所示:
(2)UI就如數(shù)所示,為EditViewController關(guān)聯(lián)EditViewController.swift源文件后桩卵,再對其上面的使用到的控件進(jìn)行關(guān)聯(lián)即可。緊接著我們要實(shí)現(xiàn)一個(gè)協(xié)議倍宾,這個(gè)協(xié)議我們用來所委托回調(diào)使用雏节。這個(gè)協(xié)議可以定義在EditViewController.swift源文件中。在協(xié)議定義之前高职,先對什么是協(xié)議簡單的提上一嘴钩乍。先簡單的理解,協(xié)議中的方法只有聲明怔锌,沒有實(shí)現(xiàn)寥粹,并且使用protocol關(guān)鍵自進(jìn)行聲明变过,下方的代碼就是我們要使用的協(xié)議。協(xié)議中有一個(gè)fetchGirlName(name:String)的方法涝涤,用來回調(diào)出輸入的數(shù)值媚狰。默認(rèn)方法是必選的,你可以使用optional關(guān)鍵字使方法可選阔拳,在此就不做過多贅述了崭孤。
1protocol EditViewControllerDelegate: NSObjectProtocol{2? ? func fetchGirlName(name:String)3}
(3) 接著要實(shí)現(xiàn)EditViewController類中的東西了,代碼如下糊肠。
成員變量var?girlOldName:String?負(fù)責(zé)接收上個(gè)頁面?zhèn)鬟^來的美女的姓名辨宠。weak?var?delegate:?EditViewControllerDelegate??這個(gè)聲明為weak的delegate成員變量則是必須要實(shí)現(xiàn)EditViewControllerDelegate協(xié)議的委托代理者,使用weak修飾為了避免強(qiáng)引用循環(huán)货裹。接著是girlNameTextField就是關(guān)聯(lián)的輸入框了嗤形,負(fù)責(zé)接收用戶輸入,把值交付給委托代理者弧圆。
在viewWillDisappear方法中赋兵,會將用戶輸入的值交付給委托代理者的fetchGirlName方法。deinit是析構(gòu)函數(shù)墓阀,用來觀察是否引起強(qiáng)引用循環(huán)毡惜,因?yàn)槲覀兪鞘褂玫膚eak, 所以不會引起強(qiáng)引用循環(huán),該deinit方法當(dāng)返回時(shí)斯撮,是會被釋放掉的经伙。
1class EditViewController: UIViewController { 2 3vargirlOldName:String? 4weakvardelegate: EditViewControllerDelegate? 5@IBOutletvargirlNameTextField: UITextField! 6 7 8override func viewDidLoad() { 9? ? ? ? super.viewDidLoad()10ifself.girlOldName != nil {11self.girlNameTextField.text = self.girlOldName!12? ? ? ? }13? ? }1415override func viewWillDisappear(animated: Bool) {16let name:String! = self.girlNameTextField.text17ifname !="" {18ifdelegate!= nil {19delegate!.fetchGirlName(name)20? ? ? ? ? ? }21? ? ? ? }22? ? }2324override func didReceiveMemoryWarning() {25? ? ? ? super.didReceiveMemoryWarning()26? ? }2728? ? deinit {29print("釋放")30? ? }31}
2.上面的代碼是實(shí)現(xiàn)編輯頁面并實(shí)現(xiàn)相應(yīng)的委托協(xié)議,下方就是要從之前TableView中進(jìn)行跳轉(zhuǎn)勿锅。也就是點(diǎn)擊TableView的每一行帕膜,然后跳轉(zhuǎn)到編輯頁面對其當(dāng)前點(diǎn)擊的cell進(jìn)行編輯,編輯后返回通過代理進(jìn)行值的修改溢十。
(1)首先要解決的就是點(diǎn)擊Cell跳轉(zhuǎn)到EditViewController, 要執(zhí)行這個(gè)事件垮刹,我們還必須實(shí)現(xiàn)TableView的另一個(gè)協(xié)議,就是UITableViewDelegate, 以為點(diǎn)擊Cell的事件獲取的方法就在TableViewDelegate中张弛。所以我們要在TableView所在的ViewController中的viewDidLoad()中指定UITableViewDelegate的委托代理者荒典。如下所示。同時(shí)該ViewContoller也要實(shí)現(xiàn)UITableViewDelegate協(xié)議吞鸭。
1self.myTableView.delegate= self
(2) 實(shí)現(xiàn)UITableViewDelegate協(xié)議中點(diǎn)擊Cell的方法寺董,方法中的內(nèi)容如下所示。在該方法中刻剥,首先我們要暫存一下點(diǎn)擊的是哪個(gè)Cell, 也就是記錄一下點(diǎn)擊Cell的IndexPath, 然后就是獲取點(diǎn)擊的Cell對象遮咖,因?yàn)橥ㄟ^該Cell對象,可以獲取相應(yīng)Cell上的數(shù)據(jù)造虏。具體的不多說了御吞,請看代碼中的注釋麦箍。
1//-----------UITableViewDelegate------------------ 2? ? func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 3 4//記錄當(dāng)前點(diǎn)擊的IndexPath 5self.selectIndexPath = indexPath 6 7//獲取當(dāng)前點(diǎn)擊的Cell對象 8let currentSelectCell:BeautifulGrillCell? = self.myTableView.cellForRowAtIndexPath(indexPath)as? BeautifulGrillCell 910//從storyboard中實(shí)例化編輯視圖控制器11let editViewController:EditViewController = UIStoryboard(name:"Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("EditViewController")as! EditViewController1213//指定編輯視圖控制器委托代理對象14editViewController.delegate= self1516//把點(diǎn)擊Cell上的值傳遞給編輯視圖控制器17ifcurrentSelectCell != nil {18editViewController.girlOldName = currentSelectCell!.girlNameLable.text!19? ? ? ? }2021//push到編輯視圖控制器22self.navigationController?.pushViewController(editViewController, animated:true)23}
(3)上面是跳轉(zhuǎn),接下來就是要實(shí)現(xiàn)EditViewControllerDelegate中的回調(diào)方法陶珠,來處理相應(yīng)的回調(diào)參數(shù)了挟裂。下方就是在表視圖中實(shí)現(xiàn)的回調(diào)方法,具體請看代碼中的注釋:
1//-----------EditViewControllerDelegate------------------ 2 3? ? func fetchGirlName(name: String) { 4 5ifselectIndexPath != nil { 6//獲取當(dāng)前點(diǎn)擊Cell的索引 7let index = (selectIndexPath?.row)! 8 9//更新數(shù)據(jù)源中相應(yīng)的數(shù)據(jù)10self.dataSource![index][GIRL_NAME] = name1112//重載TableView13? ? ? ? ? ? self.myTableView.reloadData()14? ? ? ? }1516}
經(jīng)過上面的步驟背率,我們就可以去定義屬于自己的協(xié)議话瞧,并在此協(xié)議上實(shí)現(xiàn)委托回調(diào)了。上面的場景在iOS開發(fā)中極為常見寝姿,使用場景也是比較廣泛的交排。所以協(xié)議無論在Swift還是在iOS開發(fā)中都是極為重要的概念之一。好今天的博客內(nèi)容也挺多的了饵筑,就到此為止埃篓,剩下的東西,會在以后的博客中繼續(xù)更新根资。