iOS基础首页投稿(暂停使用,暂停投稿)征服iOS

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

2016-05-07  本文已影响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的需求,这些需求我们在开发过程中是一定会遇到的:

我们同时也会接触到一些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的改变方式可能有很多种情况, 随之以上三个参数有多种组合

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

我们无法通过上面的方法来决定一个字符在发生改变后能否被显示, 因为一个名为 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突然崩溃肿么办?一旦发生这个问题, 我们将会丧失所有的输入.所以, 最好是每点一次键盘都保存数据啦.

上一篇下一篇

猜你喜欢

热点阅读