协议与委托代理回调在以前的博客中也是常常提到和用到的在《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