在这篇简单的Demo中使用RxSwift,所以我们要先导入Rx框架
import RxSwift
import RxCocoa
(如果不懂怎么导入RxSwift或者还未学习使用方法。 👉点击这里)
接下来直奔主题。
既然是一个一个的输入框,那么我们是可以用数组去包装textField的
然后我们要监控当前输入到第几个位置,那么还需要一个下标和一共是几位数的验证码
我们可以单独写一个属性去获取输入完成后的验证码
我们先创建一个ViewController,并在其中写入所要用到的属性
class HelloViewController: UIViewController {
///所有的单格textField数组
fileprivate var fields: Array<UITextField>!
///当前输入的位置
fileprivate var fieldIndex = 0
///一共是几位数的验证码
fileprivate let codeNum = 6
///获取完成输入后的code
fileprivate var code: String {
var codeStr = ""
self.fields.forEach {
guard let codeChar = $0.text else { return }
guard !codeChar.isEmpty else { return }
codeStr += codeChar
}
return codeStr
}
}
好的,我们把需要用到的属性都给放上去了,那么思路是如何进行下去的。
既然是数组,那么我们就先完成创建textField的任务
fileprivate func setupCodeField(_ index: Int) -> UITextField {
let field = UITextField()
view.addSubview(field)
field.textAlignment = .center
field.keyboardType = .numberPad
field.borderStyle = .roundedRect
let mainWidth = Int(UIScreen.main.bounds.maxX)
let width = (mainWidth - 15 * (codeNum + 1)) / codeNum
let x = 15 + (width + 15) * index
field.frame = CGRect(x: x, y: 200, width: width, height: width)
return field
}
创建的调用也很简单,我们直接在viewDidLoad里面写入一行代码
fields = (0..<codeNum).map { setupCodeField($0) }
这里,我们使用了map方法,它接受一个closure的参数,这个closure自身的参数是原数组中值的类型(Int)
走到这里,我们再整理下思路
可能会觉得疑惑的是我并没有使用UITextFieldDelegate,不然我们要怎么监听输入的值进行跳转下一个输入窗呀
RxSwift中其实已经给出了办法,在UITextField中我们可以使用rx.text进行监听text的改变
而这个rx.text的值,就是每一次用户输入之后,当前UITtextField中的字符串(不是每一次用户输入的单个字符)。
再使用subscribe订阅输入事件发生之后的处理
fileprivate func setupField(_ field: UITextField) {
field.rx.text.subscribe(onNext: {
print($0)
})
}
在setupCodeField()里面调用setupField(field)
我们进行一些输入可以看到如下打印
接下来我们需要对输入的内容进行一些限制,只能接收数字,只能输入一个字节
_ = field.rx.text
.orEmpty
//filter进行是否为数字的过滤判断
.map { $0.filter { $0.isNumber } }
//限制长度为1
.map { String($0[0 ..< ($0.count < 1 ? $0.count : 1)]) }
//subscribe订阅前面的map之后成功的"事件数组"
.subscribe (onNext :{
//重新给field.text赋值
field.text = $0
})
完成之后我们就完成了每个输入格都只能输入一个并且只能是数字的值
下一步我们对输入完数字之后,跳转到下一个格子的处理,并且打印出验证码
利用controlEvent监听editingChanged(输入内容发生改变)的事件
//在setupField()继续添加代码
let disposed = DisposeBag()
field.rx.controlEvent(.editingChanged)
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
//如果值不为空则说明写入验证码成功
guard field.text != "" else { return }
//判断是否要跳下一个输入窗
guard index < self.codeNum - 1 else {
//不跳说明到最后一个并写入成功
guard self.code != "" else { return }
print(self.code)
return
}
DispatchQueue.main.async {
self.fieldIndex = index + 1
self.fields[index + 1].becomeFirstResponder()
}
}).disposed(by: disposed)
我们command+R运行一下便可看到效果
看似已经完成了需求,不过再细想一下,
如果我们需要不管点击哪个输入框都要将光标移动到未输入的框的第一个呢,而且点击删除按钮也需要逐个点击。
既然需求出来了,就继续写入代码
这次我们监听editingDidBegin(开始编辑)
//在setupField()继续添加代码
field.rx.controlEvent(.editingDidBegin)
.subscribe(onNext: { [weak self] in
//如果当前输入位置不等于当前进行输入的位置,就需要进行下一步处理
guard let self = self,
self.fieldIndex != index else {
return
}
DispatchQueue.main.async {
//在主线程让当前输入位置的textField获取响应
self.fields[self.fieldIndex].becomeFirstResponder()
}
}).disposed(by: disposeBag)
删除事件的处理
(继承UITextField,重写deleteBackward(),并把之前的UITextField统一使用CodeTextField)
class CodeTextField: UITextField {
///回退上一个输入框的闭包
var backOneClosure: (()->())?
override func deleteBackward() {
print("isDelete")
//这里作判断的意思是如果当前输入框有值,
//那么根据前面的代码可以得出这是最后一个输入框,
//则清空最后一个输入框并且不调用backOneClosure闭包
if text != "" {
text = ""
return
}
backOneClosure?()
}
}
//回到setupField()继续添加代码
//按下删除键的回调
field.backOneClosure = { [weak self] in
//如果不是第0个,
//才可以进行数组的取值
guard index != 0,
let lastField = self?.fields[index - 1] else {
return
}
//将当前输入位置-1
self?.fieldIndex = index - 1
DispatchQueue.main.async {
lastField.text = ""
lastField.becomeFirstResponder()
}
}
command+R运行,我们的单格验证码便完成了
目前仍然在自学RxSwift,之后会放出更多demo。喜欢就点个关注吧~