[iOS] UITextField基础和一些高级功能

作者: 芮星晨 | 来源:发表于2016-05-07 14:16 被阅读5102次

    本文翻译自 https://grokswift.com/uitextfield/


    在iOS中,Apple为我们提供了三种可以显示和接收字符输入的方式:UILabel, UITextField, UITextView, 什么时候该使用哪种方式有时候也会令人非常困惑.

    如果你仅仅需要显示一些文字而不需要输入文字,那么需要使用UILabel, 有时候你可能会听到使用UITextView来显示特殊格式的文字的这种说法, 但是这已经过时了, 如今使用AttributedString你也可以做UITextView能做的大部分事情, 一般情况下应该首先尝试使用UILabel, 之后如果真的需要再使用UITextView.

    如果你需要接收用户的输入,那么你需要使用UItextField或者UITextView, 如果仅仅只有一行文字,你应该使用UItextField, 有多行文字的话, 应该使用UItextView.

    我自己开发的APP一般有很多UILabel,有一些UITextField,同时只有很少的UITextView,现在我们来看看我们使用UITextField的需求,这些需求我们在开发过程中是一定会遇到的:

    • 限制输入字符数量
    • 只允许输入特定字符(或者不允许特定字符)
    • 保存输入的内容并且在APP再次打开的时候还原内容
    • 点击返回键收回键盘

    我们同时也会接触到一些UITextField内建的显示方式和行为

    建立项目

    本案例基于Swift2.0和Xcode7.1

    为了一起愉快地玩耍, 我们新建一个SingleViewApplication, 并且拖一个UITextField进去, 给它加上约束.如图

    约束 效果

    UITextField 绑定一个属性, 同时让ViewController成为它的代理.

    class ViewController: UIViewController, UITextFieldDelegate {
     @IBOutlet weak var textField: UITextField!
     ...
    }
    
    详情

    现在我们就已经准备好来大干一番了.

    限制输入的字符数量

    使用UITextField的时候, 限制输入的字符数量是一个十分普遍的要求, 你可以在UITextField的代理方法中实现这个要求.

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
    

    上面这个方法会在textField中输入的字符发生改变的时候触发, 它有三个参数:

    1. textFieldToChange: UITextField --哪个UITextField发生了改变
    2. shouldChangeCharactersInRange range: NSRange --发生改变的跨度(包含开始位置和长度),我们稍后会详细阐述这个参数的作用
    3. replacementString string: String --增加的字符,如果是删除字符的话, 这个值为空

    textField的改变方式可能有很多种情况, 随之以上三个参数有多种组合

    • 增加字符: range为空,replacementString的长度是1
    • 删除字符: range是1(如果是剪切或者选择了多个字符跨度会更大), replacementString为空
    • 在字符中粘贴(一次性增加多个字符): range为空(因为没有字符被选择), replacementString长度大于1
    • 清除全部字符(通过剪切操作或者点击清除按钮): range大于1个字符,replacementString为空
    • 通过粘贴或者输入替换了选中的字符: range大于1, replacementString长度大于1

    自动纠正功能和上面的粘贴类似: 可增加多个字符或者替换任意选中的字符

    我们无法通过上面的方法来决定一个字符在发生改变后能否被显示, 因为一个名为 shouldChangeCharactersInRange的方法会在字符改变之前被触发,因此我们不能仅仅是在发生改变之后去检查字符的变化.

    为了限制输入的字符的数量,我们不需要知道具体的字符是什么, 仅仅知道他们的长度就足矣.我们需要先计算改变之前的字符的长度, 再加上将要增加的字符的长度, 如果他们的和小于限制数量则放行, 否则就将超出的部分删掉.

    // 先计算出改变之后的字符串总长度
    let startingLength = textFieldToChange.text?.characters.count ?? 0
    let lengthToAdd = string.characters.count
    let lengthToReplace = range.length
    let newLength = startingLength + lengthToAdd - lengthToReplace
    

    在Swift中我们需要通过字符来计算String的长度

    let stringLength = myString.characters.count
    

    我们使用空合运算来保证无法获取原始字符串长度的时候将startingLength设置为 0 (关于空合运算, 大家可以参考文末的相关链接, 文章的作者只是很简单的介绍,和主旨不符,不再翻译)

    将计算过程放在具体的代理方法中, 使用一个局部变量characterCountLimit来表示对字符数量的限制, 之后我们就可以计算出字符的改变是否超出范围了.

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
      // 设置字符限制为4个字符
      let characterCountLimit = 4
      
      // 先计算出改变之后的字符串总长度
      let startingLength = textFieldToChange.text?.characters.count ?? 0
      let lengthToAdd = string.characters.count
      let lengthToReplace = range.length
      
      let newLength = startingLength + lengthToAdd - lengthToReplace
      
      return newLength <= characterCountLimit
    }
    

    当总长度小于或者等于设定的限制数目时会允许输入,否则不会允许输入.

    现在我们可以运行这个项目并且进行测试看看是否有效果了, 如果是模拟器,还可以使用 CMD + CTRL + Z 来模拟摇晃设备产生撤销功能.

    禁止输入某个字符

    对输入进textField的字符进行过滤和进行长度限制其实并没有什么不同, 我们也是在字符完成输入之前进行判断输入的有效性, 因此我们还是使用和上文相同的代理方法:

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
    

    假设我们的需求是不允许输入标点符号, 那么我们可以通过NSCharacterSet来检查输入的字符中是否包含标点符号:

    let characterSetNotAllowed = NSCharacterSet.punctuationCharacterSet()
    

    如果你需要创建一个自定义的NSCharacterSet, 最简单的方法是通过String来创建:

    let characterSetAllowed = NSCharacterSet(charactersInString: "abcd")
    

    检查一个string是否包含一个NSCharacterSet中的元素, 我们使用rangeOfCharacterFromSet方法来实现:

    let rangeOfCharacter = string.rangeOfCharacterFromSet(characterSetNotAllowed, options: .CaseInsensitiveSearch)
    

    上面的rangeOfCharacter包含了characterSetNotAllowed这个NSCharacterSet中的某个元素第一次出现时的位置, 通过它我们可以做我们想做的了, 如果含有标点符号,我们在代理方法中返回false:

    if let _ = string.rangeOfCharacterFromSet(characterSetNotAllowed, options: .CaseInsensitiveSearch) { 
      return false // they're trying to add not allowed character(s)
    } else { 
      return true // all characters to add are allowed
    }
    

    最后整个代理方法就像这样:

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
      let characterSetNotAllowed = NSCharacterSet.punctuationCharacterSet()
      if let _ = string.rangeOfCharacterFromSet(characterSetNotAllowed, options: .CaseInsensitiveSearch) {
        return false
      } else {
        return true
      }
    }
    

    现在保存文件并运行, 测试一下我们的代码(我相信没什么问题).

    **注意: **
    本方法只在用户输入的时候起作用, 通过代码直接向textField填写字符的时候是不起作用的.

    只允许某些字符

    如果情况变了, 我们希望能够只允许某些特定的字符被输入, 其他字符一律不准输入, 怎么办? 当然还是通过rangeOfCharacterFromSet啦, 我们只需要检查所有输入的字符都在characterSetAllowed中即可, 直接上代码:

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
      let characterSetAllowed = NSCharacterSet.punctuationCharacterSet()
      if let rangeOfCharactersAllowed = string.rangeOfCharacterFromSet(characterSetAllowed, options: .CaseInsensitiveSearch) {
        // make sure it's all of them
        return rangeOfCharactersAllowed.count == string.characters.count
      } else  {
        // none of the characters are from the allowed set
        return false
      }
    }
    

    保存并运行, 测试一下吧......然后你就会苦逼地发现有BUG.
    当尝试删除标点符号的时候, 你会发现无法删除已经存在的标点符号, 这也是为什么我们需要对代码进行测试, 即使这份代码看起来非常简单并且能够实现预期的功能.这也是为啥我从来不和别人说这就是个简单的东西, 二十分钟就能搞定 的原因. 现在我们来修复这个BUG.

    当我们尝试删除字符的时候, rangeOfCharactersAllowed的值是nil,因为没有字符被改变(前文有介绍), 因此我们需要添加一个判断来允许删除字符.我们一直都在忙着阻止用户输入某些特定字符, 同样的,一旦检测到string为空的时候,我们也可以允许用户的输入嘛.

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
      let characterSetAllowed = NSCharacterSet.punctuationCharacterSet()
      if string.isEmpty
      { // allow deletion
        return true
      }
      else if let rangeOfCharactersAllowed = string.rangeOfCharacterFromSet(characterSetAllowed, options: .CaseInsensitiveSearch)
      {
        // make sure it's all of them
        return rangeOfCharactersAllowed.count == string.characters.count
      }
      else // none of the characters are from the allowed set
      {
        return false
      }
    }
    

    处理多个textField

    如果你的 view controller是多个textField的代理, 那么就需要对这些textField进行区分.

    func textField(textFieldToChange: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
      if textFieldToChange == usernameField {
        // handle username rules
        return shouldChangeUsernameTextField
      } else if textFieldToChange == passwordField {
        // handle password rules
        return shouldChangePasswordTextField
      }
      return true
    }
    

    点击返回键的时候收回键盘

    通常情况下, 点击返回键将会向textField输入一个换行符,因为textField只能显示一行,所以实际上点击返回键后什么也不会发生.通过代理方法, 我们可以设置点击返回后的事件.

    func textFieldShouldReturn(textField: UITextField) -> Bool {
      textField.resignFirstResponder()
      return true
    }
    

    当app请求textField进行返回的时候我们取消textField的第一响应者标志, 这将会让键盘收回同时移除textField的焦点.

    保存输入的内容

    如果需要在一个会进行多次开启和关闭的app中保存输入的内容, 我们需要把保存的内容存储在一个地方, 因为仅仅是保存一些字符, 因此我们可以使用NSUserDefaults来实现.

    class ViewController: UIViewController, UITextFieldDelegate {
      @IBOutlet weak var textField: UITextField!
      let textFieldContentsKey = "textFieldContents"
    
      ...
    
      func saveText() {
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setValue(textField.text, forKey: textFieldContentsKey)
      }
    }
    

    在view显示之前, 我们检查一下之前知否保存了输入数据.

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        // load text from NSUserDefaults
        let defaults = NSUserDefaults.standardUserDefaults()
        if let textFieldContents = defaults.stringForKey(textFieldContentsKey) {
          textField.text = textFieldContents
        } else {
          // focus on the text field if it's empty
          textField.becomeFirstResponder()
        }
      }
    }
    

    textField.becomeFirstResponder()这行代码让textField获取焦点, 并且弹出键盘.

    编辑完成的时候保存数据

    我们需要明确到底什么时候保存输入数据才是合适的, 最简单的方法莫过于在编辑结束的时候了,我们可以通过textField的代理来实现

    func textFieldDidEndEditing(textField: UITextField) {
      saveText()
    }
    

    但是...从用户体验上来说, 这绝对不是一个好主意. 如果输入的时候app突然崩溃肿么办?一旦发生这个问题, 我们将会丧失所有的输入.所以, 最好是每点一次键盘都保存数据啦.

    相关文章

      网友评论

        本文标题:[iOS] UITextField基础和一些高级功能

        本文链接:https://www.haomeiwen.com/subject/wmjflttx.html