MBTextFieldWithInputValidator
这个库其实 4 个月以前就已经用 Swift2.0 实现并上传到了我的github,之后一直想写篇博客分享一下我写这个库时的一些思路,但是因为各种事情,一直到今天才开始写这篇博客。当然目前的设计肯定也不是那么完美,如果有好的建议,请直接在下方评论回复,qqq~
实现对UITextField输入内容进行验证的功能,如果内容不符合验证策略,则弹出错误信息提示用户
原来项目中使用的验证器和 UITextField
控件本身是分离的,设计的思路如下:
建一个验证器类,然后为每种验证策略(如:手机,密码等)提供一个验证方法,传入参数为 UITextField
的内容,验证通过返回 true
,否则返回 false
。
而验证的步骤如下:
UITextField
中的内容。UITextField
中的内容调用相应的验证方法来进行验证。这种方式有如下几个硬伤:
在这种情况下,我们就需要设计另外一种更优雅的验证方式,当时部门总监提供了设计思路的一个雏形,然后由我进行了 OC 的实现,第一版只能给每个 UITextField
提供一种验证(比如验证是否手机),后来我用 Swift 实现了第二版,实现了为每个 UITextField
提供多种验证的功能(比如可以先验证是否为空,再验证是否手机)。
下面是具体的设计思路。
主要模块及功能如下:
MBInputValidator
:验证器基类,负责提供统一的接口供 MBTextFieldWithInputValidator
调用,验证器子类通过继承并重写其验证策略方法即可以实现具体的验证策略。MBTextFieldWithInputValidator
:UITextField
子类,负责提供统一的外部接口供业务代码调用以及验证出错时弹出告警信息。MBInputValidator
验证器基类,因为要实现顺序的多个验证的功能,所以它需要包含指向下一个验证器的属性:
@IBOutlet var next:MBInputValidator?
定义为可选值的原因是最后一个验证器的 next
为 nil
。
有了 next
,我们就需要一个特殊的构造器:
convenience init(next:MBInputValidator?) {
self.init()
self.next = next
}
这个构造器会在后面设置 UITextField
控件验证器的时候使用。
它还包含一个错误描述的内部类:
class ErrorDesc {
init(title:String, leading:String, trailing:String) {
self.title = title
self.leading = leading
self.trailing = trailing
}
var title:String? //错误信息的标题(如:温馨提示)
var leading:String? //错误信息的前缀,用来拼接到输入控件名字的头部(请输入手机号)
var trailing:String? //错误信息的后缀,用来拼接到输入控件名字的尾部(输入控件名字是:密码,trailing 是:须由6-12位的字母和数字组成,错误信息就是:密码须由6-12位的字母和数字组成)
}
然后有一个供 MBTextFieldWithInputValidator
调用的统一接口:
func validateInput(input:UITextField) -> ErrorDesc?{
return nil
}
这个方法传入 UITextField
控件 input
,然后返回 ErrorDesc
错误描述信息,验证器子类就是重写这个方法来实现具体的验证策略,来看一个密码的验证策略:
override func validateInput(input:UITextField) -> ErrorDesc?{
if false == super.validateInput(input, regexString: "^[A-Za-z0-9]{6,12}$") {
return ErrorDesc(title: "温馨提示", leading: "", trailing: "须由6-12位的字母和数字组成")
}
return nil
}
这里面有调用了 super
的另外一个 validateInput
方法,内容如下:
func validateInput(input:UITextField, regexString:String?) -> Bool{
if nil == regexString { // 如果正则表达式为空做非空判断
// 首先做去空格处理
let trim = input.text?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
return 0 != trim?.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
}else { // 否则做正则表达式判断
do {
let regex:NSRegularExpression = try NSRegularExpression(pattern: regexString!, options: NSRegularExpressionOptions.AnchorsMatchLines)
let numberOfMactches = regex.numberOfMatchesInString(input.text!, options: NSMatchingOptions.Anchored, range: NSMakeRange(0, (input.text?.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))!))
if 0 == numberOfMactches {
return false
}
} catch {
return false
}
return true
}
}
这个方法这个方法传入 UITextField
控件 input
和正则表达式串 regexString
,它负责具体的验证过程,验证成功返回 true
,否则返回false
,采用正则表达式匹配的方式是目前主流的做法。
MBTextFieldWithInputValidator
UITextField
子类,使用时需要将 UITextField
控件的类型指定为:MBTextFieldWithInputValidator
(这是目前这个库中已知的不足点,子类的方式侵入性太强,后期会用 extension
的方式重新实现)。它包含了指向第一个验证器的属性:
@IBOutlet var inputValidator:MBInputValidator?
然后有一个供业务功能调用的方法:
func validate(inputName:String, shouldAlert:Bool) -> MBInputValidator.ErrorDesc? {
// 调用另外一个私有的 validate 方法
let error = self.validate(self.inputValidator)
if nil != error {
let errorReason = (error?.leading)!+inputName+(error?.trailing)!
if true == shouldAlert { // 如果需要显示错误信息就弹出错误对话框
self.showAlertMessage((error?.title)!, message: errorReason)
}
}
return error
}
这个方法传入控件名 inputName
和 是否弹窗告警 shouldAlert
,然后返回 ErrorDesc
错误描述信息,从而也可以让业务自己去做错误显示处理。
在上面的代码中有调用一个私有的 validate
方法,其内容如下:
private func validate(validator:MBInputValidator?) -> MBInputValidator.ErrorDesc?{
if nil == validator {
return nil
}
let ret = self.validate(validator!.next)
if nil != ret {
return ret
}
return validator!.validateInput(self)
}
这个方法是实现链式验证的关键,之前已经说过,每个验证器都包含指向下一个验证器的属性,这样所有的验证器就是以链表的方式连接在一起。所以我们以递归的方式一直到拿到最后一个验证器,然后在递归栈 pop
的时候调用每个验证器的 validateInput
方法。因为是 pop
的时候调用,所以最后一个验证器的验证策略会最先做验证。
phoneField.inputValidator = MBPhoneInputValidator(next:MBNumberInputValidator(next:MBEmptyInputValidator()))
上面的代码给 nickNameField
加了两项验证,先验证是否为空,再验证内容是否是数字,最后验证是否为手机。它验证器链的结构如下:
然后在提交表单时调用下面的方法即可完成验证逻辑:
if nil != phoneField.validate(phoneField.placeholder!, shouldAlert: true) {
return;
}
只需要继承 MBInputValidator
,然后重写其 func validateInput(input:UITextField) -> ErrorDesc?
即可。
这样就实现了验证策略和业务的分离,也便于后期扩展和维护。
想要进一步了解的,可以点击文章顶部的地址下载demo。