这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
在 App 的注册、登陆流程中,经常会有验证手机验证码的流程。为了方便、清晰的显示用户输入的验证码,设计师会对用户输入的过程进行特殊的设计。
例如下面是「得到」APP 的手机验证码的输入 UI。
如果定制验证码的输入框呢?
在接收用户输入内容时,除了常用的是 UITextField
和 UITextView
,我们还可以自定义一个输入框。文字处理的控件需要实现 UITextInput
的协议,UITextField 和 UITextView 都实现了 UITextInput
。当我们实现了 UITextInput
协议,再绘制需要的 UI 元素,一个自定义手机验证码输入框也就实现了。
UITextInput 协议
先看 UITextInput 的定义:
public protocol UITextInput : UIKeyInput {
// 省略具体内容
}
public protocol UIKeyInput : UITextInputTraits {
// 省略具体内容
}
public protocol UITextInputTraits : NSObjectProtocol {
// 省略具体内容
}
通过 UITextInput 协议,可以获取到用户编辑的过程,比如:
- insertText(_:):插入字符
- deleteBackward():删除字符
- replace(_:withText:) 替换文本
当用户输入的内容发生变化时,调用 setNeedsDisplay()
方法通知需要重绘 UI。
自定义 UI
自定义 UI 可以通过重写(override)draw(_:)
方法中来实现。在 draw(_:)
方法中,可以绘制边框,光标和字符等 UI 元素。
比如,我们要实现一个名为 UnitField
的自定义验证码输入框,定义如下:
open class UnitField: UIControl {
}
在 draw(_:)
方法中绘制边框,字符等 UI 元素:
open override func draw(_ rect: CGRect) {
// 计算每个验证码大小
let width = (rect.size.width + CGFloat(unitSpace)) / CGFloat(inputUnitCount) - unitSpace
let height = rect.size.height
let unitSize = CGSize(width: width, height: height)
// 获取上下文信息
mCtx = UIGraphicsGetCurrentContext();
// 绘制填充
fill(rect: rect, unitSize: unitSize)
// 绘制边框
drawBorder(rect: rect, unitSize: unitSize)
// 绘制文字
drawText(rect: rect, unitSize: unitSize)
// 绘制跟踪框
drawTrackBorder(rect: rect, unitSize: unitSize)
}
其中,绘制字符时,首先计算出每个字符的位置,然后调用 NSString 的 draw(in:withAttributes:)
方法进行绘制。具体代码为:
func drawText(rect: CGRect, unitSize: CGSize) {
guard hasText else {
return
}
let attr = [NSAttributedString.Key.foregroundColor: textColor,
NSAttributedString.Key.font: textFont]
for i in 0 ..< characters.count {
let unitRect = CGRect(x: CGFloat(i) * (unitSize.width + unitSpace), y: 0, width: unitSize.width, height: unitSize.height)
let yOffset = style == .border ? 0 : borderWidth
let subString = NSString(string: String(characters[i]))
let oneTextSize = subString.size(withAttributes: attr)
var drawRect = unitRect.insetBy(dx: (unitRect.size.width - oneTextSize.width) / 2,
dy: (unitRect.size.height - oneTextSize.height) / 2)
drawRect.size.height -= yOffset
subString.draw(in: drawRect, withAttributes: attr)
}
}
另外,iOS 12 之后开始支持验证码自动填充功能。UITextInputTraits
协议中定义了 textContentType: UITextContentType
属性,将其设置为 oneTimeCode
就支持了自动填充功能。
完整的项目
如果想要深入了解,可以查看 UnitField 的完整项目代码,一个用起来像 UITextField 一样的自定义验证码输入框。
目前已支持 cocopose 使用,并支持 RxSwift。