窥探Swift之协议(Protocol)和委托代理(Delegate)回调的使用

 协议与委托代理回调在之前的博客中也是经常提到和用到的在《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为图片右边的文字。

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) 接着就是往上面这个数组中填充数据了,代码如下:

//-----------创建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并指定代理方法。代码如下:

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个数的回调方法,如下所示:

/**
    - parameter tableView: 当前要显示的TableView

    - returns:  TableView中Section的个数
    */
    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 18     }

 

    5.上面回调方法是返回Section个数的,紧接着下方就是返回每个Section中Cell个数的回调方法。Cell的个数就是数组dataSource中元素的个数。

/**
     返回每个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实例对象,代码如下所示。

/**
     返回要显示的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 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方法当返回时,是会被释放掉的。

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上的数据。具体的不多说了,请看代码中的注释。

//-----------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中的回调方法,来处理相应的回调参数了。下方就是在表视图中实现的回调方法,具体请看代码中的注释:

//-----------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开发中都是极为重要的概念之一。好今天的博客内容也挺多的了,就到此为止,剩下的东西,会在以后的博客中继续更新。

时间: 2024-11-01 16:14:02

窥探Swift之协议(Protocol)和委托代理(Delegate)回调的使用的相关文章

Swift中的协议(protocol)学习教程_python

一.引言 协议约定了一些属性与方法,其作用类似Java中的抽象类,Swift中类型通过遵守协议来实现一些约定的属性和方法.Swift中的协议使用protocol关键字来声明.Swift中的协议还有一个十分有意思的特性,协议可以通过扩展来实现一些方法和附加功能. 二.在协议中定义属性和方法 协议中定义的属性只约定名称和类型,在具体类型的实现中,其可以是存储属性也可以是计算属性,协议中还需要指定属性是可读的还是可读可写的.示例代码如下: protocol MyPortocol { //定义实例属性

窥探Swift编程之错误处理与异常抛出

在Swift 2.0版本中,Swift语言对其错误处理进行了新的设计,当然了,重新设计后的结果使得该错误处理系统用起来更爽.今天博客的主题就是系统的搞一下Swift中的错误处理,以及看一下Swift中是如何抛出异常的.在编译型语言中,错误一般分为编译错误和运行时错误.我们平时在代码中处理的错误为运行时错误,我们对异常进行处理的操作的目的是为了防止程序出现错误而导致其他的副作用,比如用户数据未保存等等. 在今天的博客中,先给出主动产生异常的几种情况,然后再给出如何处理被动异常. 一.主动退出程序的

Swift 面向协议编程入门

本文讲的是Swift 面向协议编程入门, 面向对象编程的思想没毛病,但老铁你可以更 666 的 上图这个人不是我,但这就是使用面向协议编程替换掉面向对象编程之后的感觉. 介绍 这个教程也是为了那些不知道类和结构体根本区别的人写的.我们都知道在结构体里是没有继承的,但是为什么没有呢? 如果你不知道上面问题的答案,那么花几秒钟看下下面的代码.请再次原谅我的排版,我已经让它尽可能的简单明了了. 注:译者已经改过排版了 class HumanClass { var name: String init(n

窥探Swift编程中的错误处理与异常抛出_Swift

在Swift 2.0版本中,Swift语言对其错误处理进行了新的设计,当然了,重新设计后的结果使得该错误处理系统用起来更爽.今天的主题就是系统的搞一下Swift中的错误处理,以及看一下Swift中是如何抛出异常的.在编译型语言中,错误一般分为编译错误和运行时错误.我们平时在代码中处理的错误为运行时错误,我们对异常进行处理的操作的目的是为了防止程序出现错误而导致其他的副作用,比如用户数据未保存等等. 在今天的文章中,先给出主动产生异常的几种情况,然后再给出如何处理被动异常. 一.主动退出程序的几种

窥探Swift编程之强大的Switch

之前初识Swift中的Switch语句时,真的是让人眼前一亮,Swift中Switch语句有好多特有而且特好用的功能.说到Switch, 只要是写过程序的小伙伴对Switch并不陌生.其在程序中的出镜率还是比较高档.Switch属于程序的分支语句,Switch的功能便于处理多个分支的较为复杂点的逻辑分支.能用Switch实现的代码都可以使用多个if-else分支语句进行替换. 今天这篇博客就是要看一下Swift中的Switch的不同之处,来总结一下Switch不同的特性.在Swift语言中的Sw

窥探Swift编程之别样的HelloWorld

原文:窥探Swift编程之别样的HelloWorld 从今天就开始陆陆续续的发布一些有关Swift语言的东西,虽然目前在公司项目开发中Objective-C还是iOS开发的主力军,但是在不久的将来Swift将会成为iOS开发中的新生宠儿.所以在在Xcode6.0+版本的Playground上玩一玩Swift还是很有必要的.在接下来发表的博客中主要是总结一下自己在翻译<Swift编程入门经典>(清华大学出版社出版中)这本书所学到的东西.在翻译这本书的时候,自己是一名译者,但更是一名读者,拜读原著

窥探Swift编程之在Playground上尽情的玩耍

自从苹果公司发布Swift的时候,Xcode上又多了一样新的东西---"Playground".Playground就像操场一样,可以供我们在代码的世界里尽情的玩耍,在本篇博客中就介绍如何借助Playground来快速的上手Swift编程.Playground在学习Swift语言中确实扮演着重要的角色,还是那句话,咸蛋就到这儿吧,下面就切入今天的正题,如何去创建一个Playground,又如何来使用Playground. 一.创建属于你的Playground 还是用之前的SwiftDe

窥探Swift之类的继承与类的访问权限

上一篇博客<窥探Swift之别具一格的Struct和Class>的博客可谓是给Swift中的类开了个头.关于类的内容还有很多,今天就来搞一下类中的继承以及类的访问权限.说到类的继承,接触过面向对象编程(OOP)的小伙伴并不陌生,继承就是OOP编程中几大特征之一,所以还是有必要把类的继承拎出来聊聊的.说到访问权限,这个在OOP编程中也是不可或缺的.如果你接触过其他OOP的语言,你应该对private, public, protected并不陌生.在Swift这么面向对象的编程语言中,也有类似的概

窥探Swift之基本数据类型

在上一篇博客"窥探Swift编程之在Playground上尽情的玩耍"中介绍了如何使用Playground来学习Swift语言.本篇博客就使用Playground来窥探Swift语言.千里之行始于足下,当然了,除非你是坐的高铁或者飞机.还是那句话从基础开始吧,本篇博客主要介绍一下Swift的基本数据类型.Swift中的数据类型可谓是百花齐放百家争鸣,下面就来窥探一下Swift中的基本数据类型. 一.Swift中的变量和常量 1.关键字"let" 常量,顾名思义,常量