用户键入时的Swift格式文本字段

时间:2017-03-25 09:34:02

标签: swift uitextfield uitextfielddelegate

我正在尝试格式化到期字段,以便按如下方式格式化文本:MM / YY 当用户输入时,我可以将文本字段添加到额外的字符中,但是当我删除数字时,代码将不允许您再次添加“/”之前传递两个字符。当用户删除并绕过文本字段检查时,有没有办法识别?

ExpiryOutlet.addTarget(self, action: #selector(ExpiryDidChange(_:)), for: .editingChanged)

func ExpiryDidChange(_ textField: UITextField) {
    if textField == ExpiryOutlet {

        if textField.text != "" && textField.text?.characters.count == 2 {
            textField.text = "\(textField.text!) / "
        }

    }
}

由于

2 个答案:

答案 0 :(得分:2)

您可以创建一个自定义字段,以允许用户仅通过向对象添加目标来为controlEvents editChanged添加一个自定义字段,以便更新UI。

首先让子类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()
    }
}

我们还需要通过过滤所有非数字字符来正确格式化字段文本,使用flatMap在它们的字符串表示上将它们转换为Int,并返回Int的数组,范围从0到9.我们需要根据斜杠字符设置通过切换字符串中的位数来输入用户输入的数字。考虑到它是一个到期字段,您还需要检查用户输入的月份和年份是否仍然有效。因此,我们将月份和年份属性添加到ExpirationField以返回其值。这同样适用于日期,因此我们可以将其与当前月份和年份进行比较以验证到期日期:

extension ExpirationField {
    var string : String { return text ?? "" }
    var numbers: [Int]  { return string.characters.flatMap{ Int(String($0)) } }
    var year:    Int    { return numbers.suffix(2).integer }
    var month:   Int    { return numbers.prefix(2).integer }
    func editingChanged() {
        text = expirationFormatted
        if text?.characters.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.characters.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 {
        return Calendar.iso8601.component(.year, from: self)
    }
    var month: Int {
        return Calendar.iso8601.component(.month, from: self)
    }
}

extension Collection where Iterator.Element == Int {
    var string: String {
        return map(String.init).joined()
    }
    var integer: Int { return reduce(0){ 10 * $0 + $1 } }
}

然后,您只需将文本字段拖到视图中,选择它并在检查器中将自定义类设置为ExpirationField:

enter image description here

Sample

答案 1 :(得分:0)

  

当用户删除并绕过文本字段检查时,有没有办法识别?

问题是你实施了错误的方法。实现委托方法text​Field(_:​should​Change​Characters​In:​replacement​String:​)。这使您可以区分 文本正在发生变化(第二个参数是范围),以及新文本 - 如果是退格,{{1}将是空的。