基于Swift的iOS应用程序开发:通过UITextFieldDelegate快速理解Delegate事件代理

来源:互联网 发布:固结快剪试验数据 编辑:程序博客网 时间:2024/05/18 07:34

一、简述

在使用swift语言进行iOS应用程序开发的过程中,我们会经常接触到“Delegate”这个概念。为了更好地理解这个概念,我们以文本输入框组件UITextField为例。

二、概念

在苹果官方的入门开发示例当中,关于Delegate有这样一段描述:
//*******************************************************************************************////*  To understand when these methods get called and what they need to do,                  *////*  it’s important to know how text fields respond to user events.                         *////*  When the user taps a text field, it automatically becomes the first responder.         *////*  In an app, the first responder is an object                                            *////*  that is first on the line for receiving many kinds of app events,                      *////*  including key events,motion events, and action messages, among others.                 *////*  In other words, many of the events generated by the user are initially routed          *////*  to the first responder.                                                                *////*                                                                                         *////*  As a result of the text field becoming the first responder,                            *////*  iOS displays the keyboard and begins an editing session for that text field.           *////*  What a user types using that keyboard gets inserted into the text field.               *////*                                                                                         *////*  When a user wants to finish editing the text field,                                    *////*  the text field needs to resign its first-responder status.                             *////*  Because the text field will no longer be the active object in the app,                 *////*  events need to get routed to a more appropriate object.                                *////*                                                                                         *////*  This is where your implementation of UITextFieldDelegate methods comes in.             *////*  You need to specify that the text field should resign its first-responder status       *////*  when the user taps a button to end editing in the text field.                          *////*  You do this in the textFieldShouldReturn(_:) method,                                   *////*  which gets called when the user taps Return (or in this case, Done) on the keyboard.   *////*******************************************************************************************//
这段描述并不难理解,我们来做一个简单的翻译:
//*******************************************************************************************////*     为了能够理解文本输入框的代理事件会在什么时候被调用,以及它们需要做哪些事情,//*     我们必须首先理解文本输入框是如何来响应用户所触发的事件的,这非常重要。//*     当用户轻轻地点击了一个文本输入框,它就会自动成为一个“第一响应”(原文为“the first responder”)。//*     在一个app中,第一响应会(比其它界面组件)更早接收到app事件,//*     包括键盘输入事件、滑动事件、消息、以及其它一些事件。//*     换句话说,用户所触发的很多事件,都会被转发到第一响应那儿去。//*     //*     当一个文本输入框成为了第一响应以后,iOS就会将键盘显示出来,并且开启文字输入会话。//*     当用户用这个键盘进行输入的时候,文字就会显示在作为第一响应的那个文本输入框中。//*//*     当用户希望结束文字输入的时候,文本输入框就必须放弃第一响应状态。//*     因为此时文本输入框将不再是app中的“活跃对象”(原文为“active object”),//*     而(用户所触发的)事件也必须被转发到其它(更适合的)组件对象上去。//*//*     这就是为什么你需要继承“UITextFieldDelegate”这个代理事件,并实现它的方法。//*     当用户轻轻敲击了某个按钮,来结束文字输入的时候,你必须非常明确地让文本输入框放弃第一响应状态。//*     你可以实现方法“textFieldShouldReturn(_:)”来达到这一目的,//*     这个方法会在用户点击了键盘上的“回车”键(回车键也可能会被标记成“完成”、“下一个”等等)时被调用。//*//*     以下为补漏://*     如同其它的一些代理事件一样,如果某个文本输入框被用户点击,成为了第一响应之后,//*     你希望你的程序能够立刻能够处理一些业务工作,那么你可以实现“textFieldShouldBeginEditing(_:)”方法//*******************************************************************************************//

从这段说明我们就能够非常容易理解swift中的delegate的概念了,简单地说,使用delegate能够捕获到用户在设备上所触发的各种事件,并根据我们自己所编写的代码来响应这些事件。

三、示例

文本输入框组件UITextField是非常非常常见的组件,用户可以在文本输入框中输入文字。但是如果在一个页面布局中,文本输入框的数量比较多,那么当用户在其中的某个输入框中输入文字的时候,从屏幕下方弹起来的键盘非常可能会遮挡掉位于屏幕下半部分的其它界面组件。

我们来看下面这个截图:


为了避免发生键盘遮挡界面的情况发生,我们希望当用户在某个文本输入框中输入文字的时候,其它的输入框能够隐藏起来,就像下面这样:

四、代码及实现

4.1算法简述

/** *  因为本界面上的TextField数量过多,当用户在TextField中进行输入时,界面下方 *  弹出来的虚拟键盘会遮挡掉一部分的TextField,影响用户的正常使用 *  为此,每当用户在某一个TextField中进行文字输入时,都需要将这个TextField移动到页面的顶端,确保不被键盘遮挡 * *  我所采用的方式,是整体将所有的TextField整体往上移动。 * *  移动的方法说明如下: * *  在界面上,所有的TextField都是以约束形式来固定位置的 *  每一个TextField的y轴的位置,都取决于它和在它上方的TextField之间间距多少距离 *  也就是每一个TextField的y轴的位置实际上都取决于与它紧邻的上方的那个TextField *  而最上方的一个TextField的y轴的位置,则取决于它和界面顶部距离多少 * *  所以,想要改变任何一个TextField的y轴的位置,其实都只要改变最上方的TextField的y轴的位置就可以了 * */


4.2继承代理事件

在这个解决方案中,我们希望当用户点击到某一个输入框的时候,就把界面上的其它输入框都隐藏起来,并且将当前正在输入的这个输入框提到页面的最顶端。
为此我们的类必须继承UITextFieldDelegate代理,因为我们需要用到以下两个方法:
textFieldShouldBeginEditing(_ textField: UITextField) -> BooltextFieldShouldReturn(_ textField: UITextField) -> Bool
从这两个方法的名字我们就可以看出,当用户点击某个文本输入框的时候,iOS就会自动调用到第一个方法。而当用户完成文字输入,并且点击键盘上的回车键的时候,iOS就会自动调用到第二个方法。

需要注意的是,键盘上的回车键也许显示的并不是“return”文字,在xcode中程序员可以将其指定为一些其它的文字:

4.3定义属性

/* *  这个变量表示的是界面上用于放置所有组件的最大的根容器 */var viewMainContent: UIView!    /* *  这个变量表示的是界面上的最上方第一个组件的布局约束:距离整个界面顶端的间距 */@IBOutlet weak var layoutConstraintTop: NSLayoutConstraint!/* *  这个变量是一个数组,用来存放界面上所有的纵向排列的界面组件 *  数组中的每一个元素就代表一行组件 */var viewArr:[UIView] = [UIView]()    /* *  界面上的每一个TextField的净高度 */let heightOfTextField:CGFloat = 30.0    /* *  界面上的上下相邻的两个TextField之间的间距 */let spaceBetweenTextField:CGFloat = 8.0

4.4初始化所有的文本输入框

在viewDidLoad的时候获取到界面上的所有文本输入框,并将它们放入数组中
/* *  获取界面上用于放置所有组件的最大的根容器 */self.viewMainContent = self.view        /* *  获取界面上的所有纵向排列的界面组件 */self.viewArr = self.viewMainContent.subviews

4.5为所有文本输入框设置代理

这里我单独封装了一个方法:
/** *  为当前界面上所有文本输入框注册事件代理 */func initTextFieldDelegate(){    for viewObj in self.viewArr{        if viewObj.isKind(of: UITextField.self){            let textFieldObj = viewObj as! UITextField            textFieldObj.delegate = self        }    }}
然后在viewDidLoad的时候调用这个方法:
self.initTextFieldDelegate()

4.6编写方法,实现移动文本输入框

这里分为两个方法,第一个是将文本输入框移动到页面顶端:
func resetY(viewObj:UIView){    /*     *  计算TextFiled在y轴方向上的单位移动距离     *     *  界面上的每一个TextView组件,它与和它相邻的、位于它上方的组件的间距,加上它自己的净高度     *  就是TextView在y轴方向移动时的一个单位高度     */    let distanceOfY:CGFloat = spaceBetweenTextField + heightOfTextField            /*     *  获取当前用户正在进行文字输入操作的TextField,并计算这是第几个TextField     */    let index:CGFloat = CGFloat(self.viewArr.index(of: viewObj)!)            /*     *  将第一个TextField整体往y轴的上方进行移动(即y值变小)     *  这样以来所有的TextField都会跟着一起移动     *     *  注意实际操作的其实并不是最上方的第一个TextField组件     *  而是它与页面顶端之间的“布局约束”对象     */    self.layoutConstraintTop.constant.add(0 - (index * distanceOfY))}

第二个方法是将文本输入框的位置复原:

func reloadY(viewObj:UIView){    /*     *  计算TextFiled在y轴方向上的单位移动距离     *     *  界面上的每一个TextView组件,它与和它相邻的、位于它上方的组件的间距,加上它自己的净高度     *  就是TextView在y轴方向移动时的一个单位高度     */    let distanceOfY:CGFloat = spaceBetweenTextField + heightOfTextField            /*     *  获取当前用户正在进行文字输入操作的TextField,并计算这是第几个TextField     */    let index:CGFloat = CGFloat(self.viewArr.index(of: viewObj)!)            /*     *  将第一个TextField整体往y轴的下方进行移动(即y值变大)     *  这样以来所有的TextField都会跟着一起移动     *     *  注意实际操作的其实并不是最上方的第一个TextField组件     *  而是它与页面顶端之间的“布局约束”对象     */    self.layoutConstraintTop.constant.add(index * distanceOfY)}

4.7编写方法,隐藏或显示所有其它的文本输入框

/** *  对于某一个指定的TextField,将所有在它下方的其它TextField都隐藏起来 */func hideTextFieldUnder(viewObj:UIView){    let index:Int = self.viewArr.index(of: viewObj)!    for tf in self.viewArr{        if self.viewArr.index(of: tf)! > index{            tf.isHidden = true        }    }}    /** *  对于某一个指定的TextField,将所有在它下方的其它TextField全部都显示出来 *  其实有一个更简单的方法,就是无脑地把所有的TextField都设置为“显示” *  但是不那么做是为了与本类的方法“hideTextFieldUnder(textField:TextField)”能够呼应起来 */func showTextFieldUnder(viewObj:UIView){    let index:Int = self.viewArr.index(of: viewObj)!    for tf in self.viewArr{        if self.viewArr.index(of: tf)! > index{            tf.isHidden = false        }    }}

4.8捕获事件

第一步,当用户在某个文本输入框中录入文字的时候,捕获这一事件,我们需要实现以下方法:

func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool{    /*     *  将正在进行文本输入操作的文本框移动到界面的最顶端     *  从而避免虚拟键盘将文本输入框遮挡住     */    self.resetY(viewObj: textField)            /*     *  隐藏目前正在接受文字输入的文本输入框下方的所有其它文本输入框     */    self.hideTextFieldUnder(viewObj: textField)            return true}

而当用户完成输入,点击键盘上的回车按钮的时候,我们也要捕获这一事件,此时需要实现以下方法:
func textFieldShouldReturn(_ textField: UITextField) -> Bool{    /*     *  完成在文本输入框中的文字输入操作后,需要将这个文本输入框从事件响应队列中移除     */    textField.resignFirstResponder();            /*     *  完成在文本输入框中的文字输入操作后,需要将文本输入框的位置归位     */    self.reloadY(viewObj: textField)            /*     *  显示所有下方的文本输入框     */    self.showTextFieldUnder(viewObj: textField)            return true;}

好了,以上就是一个关于文本输入框代理事件的最简单的例子。
以下贴出完整的整个UIViewController的代码:
////  关于文本输入框的事件代理,摘录苹果开发者中心的官方解释如下://*******************************************************************************************////*  To understand when these methods get called and what they need to do,                  *////*  it’s important to know how text fields respond to user events.                         *////*  When the user taps a text field, it automatically becomes the first responder.         *////*  In an app, the first responder is an object                                            *////*  that is first on the line for receiving many kinds of app events,                      *////*  including key events,motion events, and action messages, among others.                 *////*  In other words, many of the events generated by the user are initially routed          *////*  to the first responder.                                                                *////*                                                                                         *////*  As a result of the text field becoming the first responder,                            *////*  iOS displays the keyboard and begins an editing session for that text field.           *////*  What a user types using that keyboard gets inserted into the text field.               *////*                                                                                         *////*  When a user wants to finish editing the text field,                                    *////*  the text field needs to resign its first-responder status.                             *////*  Because the text field will no longer be the active object in the app,                 *////*  events need to get routed to a more appropriate object.                                *////*                                                                                         *////*  This is where your implementation of UITextFieldDelegate methods comes in.             *////*  You need to specify that the text field should resign its first-responder status       *////*  when the user taps a button to end editing in the text field.                          *////*  You do this in the textFieldShouldReturn(_:) method,                                   *////*  which gets called when the user taps Return (or in this case, Done) on the keyboard.   *////*******************************************************************************************////  简单地翻译如下://*******************************************************************************************////*     为了能够理解文本输入框的代理事件会在什么时候被调用,以及它们需要做哪些事情,//*     我们必须首先理解文本输入框是如何来响应用户所触发的事件的,这非常重要。//*     当用户轻轻地点击了一个文本输入框,它就会自动成为一个“第一响应”(原文为“the first responder”)。//*     在一个app中,第一响应会(比其它界面组件)更早接收到app事件,//*     包括键盘输入事件、滑动事件、消息、以及其它一些事件。//*     换句话说,用户所触发的很多事件,都会被转发到第一响应那儿去。//*     //*     当一个文本输入框成为了第一响应以后,iOS就会将键盘显示出来,并且开启文字输入会话。//*     当用户用这个键盘进行输入的时候,文字就会显示在作为第一响应的那个文本输入框中。//*//*     当用户希望结束文字输入的时候,文本输入框就必须放弃第一响应状态。//*     因为此时文本输入框将不再是app中的“活跃对象”(原文为“active object”),//*     而(用户所触发的)事件也必须被转发到其它(更适合的)组件对象上去。//*//*     这就是为什么你需要继承“UITextFieldDelegate”这个代理事件,并实现它的方法。//*     当用户轻轻敲击了某个按钮,来结束文字输入的时候,你必须非常明确地让文本输入框放弃第一响应状态。//*     你可以实现方法“textFieldShouldReturn(_:)”来达到这一目的,//*     这个方法会在用户点击了键盘上的“回车”键(回车键也可能会被标记成“完成”、“下一个”等等)时被调用。//*//*     以下为补漏://*     如同其它的一些代理事件一样,如果某个文本输入框被用户点击,成为了第一响应之后,//*     你希望你的程序能够立刻能够处理一些业务工作,那么你可以实现“textFieldShouldBeginEditing(_:)”方法//*******************************************************************************************//////  DemoTextFieldDelegateViewController.swift//  MyDemo////  Created by 徐威男 on 2017/8/3.//  Copyright © 2017年 freezingxu. All rights reserved.//import UIKitclass DemoTextFieldDelegateViewController: UIViewController,UITextFieldDelegate {    //MARK:属性        /*     *  这个变量表示的是界面上用于放置所有组件的最大的根容器     */    var viewMainContent: UIView!        /*     *  这个变量表示的是界面上的最上方第一个组件的布局约束:距离整个界面顶端的间距     */    @IBOutlet weak var layoutConstraintTop: NSLayoutConstraint!    /*     *  这个变量是一个数组,用来存放界面上所有的纵向排列的界面组件     *  数组中的每一个元素就代表一行组件     */    var viewArr:[UIView] = [UIView]()        /*     *  界面上的每一个TextField的净高度     */    let heightOfTextField:CGFloat = 30.0        /*     *  界面上的上下相邻的两个TextField之间的间距     */    let spaceBetweenTextField:CGFloat = 8.0        override func viewDidLoad() {        super.viewDidLoad()                /*         *  获取界面上用于放置所有组件的最大的根容器         */        self.viewMainContent = self.view                /*         *  获取界面上的所有纵向排列的界面组件         */        self.viewArr = self.viewMainContent.subviews                /*         *  为界面上的所有文本输入框注册事件         */        self.initTextFieldDelegate()    }    override func didReceiveMemoryWarning() {        super.didReceiveMemoryWarning()        // Dispose of any resources that can be recreated.    }        /*    // MARK: - Navigation    // In a storyboard-based application, you will often want to do a little preparation before navigation    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {        // Get the new view controller using segue.destinationViewController.        // Pass the selected object to the new view controller.    }    */    /**     *  当用户点击某一个文本输入框,希望开始输入文字的时候,该方法会被立刻调用到     *  系统会将这个被点击的输入框放在事件响应器(responder)队列的第一位     *  即让这个文本输入框能够响应所有用户的屏幕操作,包括在虚拟键盘上输入文字     *     *  由于当前界面上的文本输入框数量较多,触发手机的虚拟键盘进行输入操作时,屏幕上弹出的虚拟键盘会遮挡住一部分界面     *  这对用户操作相当不友好     *  所以当用户点击某个文本输入框的时候,要让当前正在进行输入操作的文本输入框能够显示在屏幕的最上方     *  等到完成输入(即点击了虚拟键盘上的“完成(也可能是“下一步”等)”按钮)的时候,再将它归位     *     *  除了移动当前的文本输入框的位置之外,为了避免有些用户可能会搞不清楚状况     *  还需要将当前文本输入框下方的所有其它输入框全部都隐藏起来     *  这样以来,当用户在进行文本输入的时候,就保证界面上只有一个文本输入框了     *     *  苹果开发者中心官方对TextFiled组件的这些监听事件的解释摘录详见本类的头部注释     */    func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool{        /*         *  将正在进行文本输入操作的文本框移动到界面的最顶端         *  从而避免虚拟键盘将文本输入框遮挡住         */        self.resetY(viewObj: textField)                /*         *  隐藏目前正在接受文字输入的文本输入框下方的所有其它文本输入框         */        self.hideTextFieldUnder(viewObj: textField)                return true    }        /**     *     *  当用户完成在一个文本输入框中的输入后,点击虚拟键盘上的“完成(也可能是“下一步”等)”按钮,则需要移除这个文本输入框的第一响应状态     *  这个方法就是用来处理用户点击虚拟键盘上的“完成(也可能是“下一步”等)”按钮时的业务逻辑     *     *  苹果开发者中心官方对TextFiled组件的这些监听事件的解释摘录详见本类的头部注释     *     */    func textFieldShouldReturn(_ textField: UITextField) -> Bool{        /*         *  完成在文本输入框中的文字输入操作后,需要将这个文本输入框从事件响应队列中移除         */        textField.resignFirstResponder();                /*         *  完成在文本输入框中的文字输入操作后,需要将文本输入框的位置归位         */        self.reloadY(viewObj: textField)                /*         *  显示所有下方的文本输入框         */        self.showTextFieldUnder(viewObj: textField)                return true;    }        //MARK:方法------------------------------    /**     *  为当前界面上所有文本输入框注册事件代理     */    func initTextFieldDelegate(){        for viewObj in self.viewArr{            if viewObj.isKind(of: UITextField.self){                let textFieldObj = viewObj as! UITextField                textFieldObj.delegate = self            }        }    }        /**     *  因为本界面上的TextField数量过多,当用户在TextField中进行输入时,界面下方     *  弹出来的虚拟键盘会遮挡掉一部分的TextField,影响用户的正常使用     *  为此,每当用户在某一个TextField中进行文字输入时,都需要将这个TextField移动到页面的顶端,确保不被键盘遮挡     *     *  我所采用的方式,是整体将所有的TextField整体往上移动。     *     *  移动的方法说明如下:     *     *  在界面上,所有的TextField都是以约束形式来固定位置的     *  每一个TextField的y轴的位置,都取决于它和在它上方的TextField之间间距多少距离     *  也就是每一个TextField的y轴的位置实际上都取决于与它紧邻的上方的那个TextField     *  而最上方的一个TextField的y轴的位置,则取决于它和界面顶部距离多少     *     *  所以,想要改变任何一个TextField的y轴的位置,其实都只要改变最上方的TextField的y轴的位置就可以了     *     *  入参说明:     *  textField:UITextField   用户当前正在进行文字输入操作的TextField     */    func resetY(viewObj:UIView){        /*         *  计算TextFiled在y轴方向上的单位移动距离         *         *  界面上的每一个TextView组件,它与和它相邻的、位于它上方的组件的间距,加上它自己的净高度         *  就是TextView在y轴方向移动时的一个单位高度         */        let distanceOfY:CGFloat = spaceBetweenTextField + heightOfTextField                /*         *  获取当前用户正在进行文字输入操作的TextField,并计算这是第几个TextField         */        let index:CGFloat = CGFloat(self.viewArr.index(of: viewObj)!)                /*         *  将第一个TextField整体往y轴的上方进行移动(即y值变小)         *  这样以来所有的TextField都会跟着一起移动         *         *  注意实际操作的其实并不是最上方的第一个TextField组件         *  而是它与页面顶端之间的“布局约束”对象         */        self.layoutConstraintTop.constant.add(0 - (index * distanceOfY))    }        /**     *  当用户完成在一个TextField中进行输入后,需要将TextField从页面最顶端的位置恢复到它原本所在的位置     *  恢复的算法请详见本类中的方法“resetY(textField:UITextField)”     *  即“怎么修改的,就怎么恢复”     */    func reloadY(viewObj:UIView){        /*         *  计算TextFiled在y轴方向上的单位移动距离         *         *  界面上的每一个TextView组件,它与和它相邻的、位于它上方的组件的间距,加上它自己的净高度         *  就是TextView在y轴方向移动时的一个单位高度         */        let distanceOfY:CGFloat = spaceBetweenTextField + heightOfTextField                /*         *  获取当前用户正在进行文字输入操作的TextField,并计算这是第几个TextField         */        let index:CGFloat = CGFloat(self.viewArr.index(of: viewObj)!)                /*         *  将第一个TextField整体往y轴的下方进行移动(即y值变大)         *  这样以来所有的TextField都会跟着一起移动         *         *  注意实际操作的其实并不是最上方的第一个TextField组件         *  而是它与页面顶端之间的“布局约束”对象         */        self.layoutConstraintTop.constant.add(index * distanceOfY)    }        /**     *  对于某一个指定的TextField,将所有在它下方的其它TextField都隐藏起来     */    func hideTextFieldUnder(viewObj:UIView){        let index:Int = self.viewArr.index(of: viewObj)!        for tf in self.viewArr{            if self.viewArr.index(of: tf)! > index{                tf.isHidden = true            }        }    }        /**     *  对于某一个指定的TextField,将所有在它下方的其它TextField全部都显示出来     *  其实有一个更简单的方法,就是无脑地把所有的TextField都设置为“显示”     *  但是不那么做是为了与本类的方法“hideTextFieldUnder(textField:TextField)”能够呼应起来     */    func showTextFieldUnder(viewObj:UIView){        let index:Int = self.viewArr.index(of: viewObj)!        for tf in self.viewArr{            if self.viewArr.index(of: tf)! > index{                tf.isHidden = false            }        }    }}


阅读全文
0 0