您可以继承 UITextField 并创建一个自定义字段,以允许用户仅通过向您的对象添加一个目标来输入数字,以便使用选择器来更新 UI 的 controlEvents editingChanged。
首先让 UITextField 子类化:
class ExpirationField: UITextField {
var allowsExpiredDate = false
override func didMoveToSuperview() {
super.didMoveToSuperview()
placeholder = "MM/YY"
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
keyboardType = .numberPad
textAlignment = .center
editingChanged()
}
}
我们还需要正确格式化字段文本,方法是过滤所有非数字字符,将它们转换为 Int,在它们的字符串表示上使用 compactMap 并返回一个从 0 到 9 的 Int 数组。我们需要根据情况放置斜杠字符通过切换字符串中的位数来确定用户输入的位数。考虑到它是一个过期字段,您还需要检查用户输入的月份和年份是否仍然有效。因此,让我们将月份和年份属性添加到 ExpirationField 以返回它们的值。这同样适用于日期,因此我们可以将其与当前月份和年份进行比较以验证到期日期:
extension ExpirationField {
var string : String { text ?? "" }
var numbers: [Int] { string.compactMap(\.wholeNumberValue) }
var year: Int { numbers.suffix(2).integer }
var month: Int { numbers.prefix(2).integer }
@objc func editingChanged() {
text = self.expirationFormatted
if text?.count == 5 {
print("Month:", month, "Year:", year, "isValid:", isValid)
if !allowsExpiredDate && !isValid {
text = numbers.prefix(2).string + "/" + numbers.dropLast().suffix(1).string
}
} else {
print("isValid:", false)
switch numbers.count {
case 1 where numbers.integer > 1:
text = ""
case 2 :
if numbers.integer > 12 {
text = "1"
} else if numbers.integer == 0 {
text = "0"
}
case 3 where (numbers.last ?? 0) < 1 && !allowsExpiredDate:
text = numbers.dropLast().string
case 4 where year + 2000 < Date().year && !allowsExpiredDate:
text = numbers.prefix(2).string + "/" + numbers.dropLast().suffix(1).string
default:
break
}
}
if isValid {
layer.borderColor = UIColor.darkGray.cgColor
layer.cornerRadius = 3
layer.borderWidth = 1
} else {
layer.borderColor = UIColor.clear.cgColor
layer.borderWidth = 0
}
}
var expirationFormatted: String {
let numbers = self.numbers.prefix(4)
switch numbers.count {
case 1...2: return numbers.string
case 3: return numbers.prefix(2).string + "/" + numbers.suffix(1).string
case 4: return numbers.prefix(2).string + "/" + numbers.suffix(2).string
default: return ""
}
}
var isValid: Bool {
if string.count < 5 { return false }
guard 1...12 ~= month else {
print("invalid month:", month)
return false
}
guard Date().year-2000...99 ~= year else {
print("invalid year:", year)
return false
}
return year > Date().year-2000 ? true : month >= Date().month
}
override func deleteBackward() {
text = numbers.dropLast().string
text = expirationFormatted
layer.borderColor = UIColor.clear.cgColor
layer.borderWidth = 0
}
}
extension Calendar {
static let iso8601 = Calendar(identifier: .iso8601)
}
extension Date {
var year: Int { Calendar.iso8601.component(.year, from: self) }
var month: Int { Calendar.iso8601.component(.month, from: self) }
}
extension Collection where Iterator.Element == Int {
var string: String { map(String.init).joined() }
var integer: Int { reduce(0){ 10 * $0 + $1 } }
}
然后您只需将一个文本字段拖到您的视图中,选择它并在检查器中将自定义类设置为 ExpirationField: