import UIKit
enum XJLCardValidationState: Int {
case valid = 0 ,inValid = 1, complete = 2
}
class XJLCardTextField: UITextField {
override func invalidateIntrinsicContentSize() {
self.rawExpiration = self.text ?? ""
}
override var text: String? {
didSet (newVale){
self.rawExpiration = self.text ?? ""
let state = self.validationStateForField()
self.validText = true;
switch (state) {
case .inValid:
self.validText = false;
break;
case .complete:
break;
case .valid:
break;
}
let attributed = NSAttributedString(string: self.rawExpiration, attributes: defaultTextAttributes)
attributedText = attributed
}
}
override var attributedText: NSAttributedString? {
get {
return super.attributedText
}
set(attributedText) {
let modified = attributedText
super.attributedText = modified
sendActions(for: .valueChanged)
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.delegate = self
self.defaultColor = .black;
self.errorColor = .red
self.validText = true
}
override init(frame: CGRect) {
super.init(frame: frame)
self.defaultColor = .black;
self.errorColor = .red
self.validText = true
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
var errorColor: UIColor? {
didSet {
self.updateColor()
}
}
var defaultColor: UIColor? {
didSet {
self.updateColor()
}
}
var validText: Bool? {
didSet {
self.updateColor()
}
}
var rawExpiration: String {
set {
let sanitizedExpiration = self.unformattedString(for: newValue)
expirationMonth = (sanitizedExpiration as NSString).substring(to: min(2, sanitizedExpiration.count))
let temp = (sanitizedExpiration as NSString).substring(from: min(2, sanitizedExpiration.count))
expirationYear = (temp as NSString).substring(to: min(2, temp.count))
}
get {
var array: [String] = []
if expirationMonth != nil && expirationMonth != "" {
array.append(expirationMonth!)
}
if self.validationState(forExpirationMonth: expirationMonth ?? "") == .valid {
array.append(expirationYear!)
}
return array.joined(separator: "/")
}
}
var expirationMonth: String? {
didSet {
var sanitizedExpiration = self.unformattedString(for: ((expirationMonth ?? "") as NSString) as String)
if sanitizedExpiration.count == 1 && !(sanitizedExpiration == "0") && !(sanitizedExpiration == "1") {
sanitizedExpiration = "0" + sanitizedExpiration
}
expirationMonth = (sanitizedExpiration as NSString).substring(to: min(2, sanitizedExpiration.count))
}
}
var expirationYear: String? {
didSet {
let temp = (self.unformattedString(for: expirationYear ?? "")
as NSString)
expirationYear = temp.substring(to: min(2, temp.length))
}
}
func validationState(forExpirationMonth expirationMonth: String) -> XJLCardValidationState {
let sanitizedExpiration = self.unformattedString(for: expirationMonth)
if !((sanitizedExpiration as NSString).rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789").inverted).location == NSNotFound) {
return .inValid
}
switch sanitizedExpiration.count {
case 0:
return .complete
case 1:
return (((sanitizedExpiration == "0") || (sanitizedExpiration == "1")) ? .complete : .valid)
case 2:
return ((0 < Int(sanitizedExpiration) ?? 0 && Int(sanitizedExpiration) ?? 0 <= 12) ? .valid : .inValid)
default:
return .inValid
}
}
func validationStateForField() -> XJLCardValidationState {
let monthState = self.validationState(forExpirationMonth: expirationMonth ?? "")
let yearState = self.validationState(forYear: expirationYear, inMonth: expirationMonth)
if monthState == .valid && yearState == .valid {
return .valid
} else if monthState == .inValid || yearState == .inValid {
return .inValid
} else {
return .complete
}
}
func validationState (
forYear expirationYear: String?,inMonth expirationMonth: String?) -> XJLCardValidationState {
return self.validationState(
forExpirationYear: expirationYear,
inMonth: expirationMonth,
inCurrentYear: self.currentYear(),
currentMonth: self.currentMonth())
}
func currentYear() -> Int {
let calendar = Calendar(identifier: .gregorian)
let dateComponents = calendar.component(.year, from: Date())
return (dateComponents) % 100
}
func currentMonth() -> Int {
let calendar = Calendar(identifier: .gregorian)
let dateComponents = calendar.component(.month, from: Date())
return dateComponents
}
func validationState(forExpirationYear expirationYear: String?, inMonth expirationMonth: String?, inCurrentYear currentYear: Int, currentMonth: Int) -> XJLCardValidationState {
let moddedYear = currentYear % 100
if !self.stringIsNumeric(expirationMonth) || !self.stringIsNumeric(expirationYear) {
return .inValid
}
let sanitizedMonth = self.unformattedString(for: expirationMonth ?? "")
let sanitizedYear = self.unformattedString(for: expirationYear ?? "")
switch sanitizedYear.count {
case 0, 1:
return .complete
case 2:
if validationState(forExpirationMonth: sanitizedMonth) == .inValid {
return .inValid
} else {
if Int(sanitizedYear) == moddedYear {
return (Int(sanitizedMonth) ?? 0) >= currentMonth ? .valid : .inValid
} else {
return (Int(sanitizedYear) ?? 0) > moddedYear ? .valid : .inValid
}
}
default:
return .inValid
}
}
func stringIsNumeric(_ string: String?) -> Bool {
return (string as NSString?)?.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789").inverted).location == NSNotFound
}
func updateColor() {
textColor = validText ?? true ? defaultColor : errorColor
}
}
extension XJLCardTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let insertingIntoEmptyField = textField.text!.count == 0 && range.location == 0 && range.length == 0
var hasTextContentType = false
if #available(iOS 11.0, *) {
hasTextContentType = textField.textContentType != nil
}
if hasTextContentType && insertingIntoEmptyField && (string == " ") {
return true
}
let deleting = range.location == textField.text!.count - 1 && range.length == 1 && (string == "")
var inputText: String?
if deleting {
let sanitized = self.unformattedString(for: textField.text ?? "")
inputText = (sanitized as NSString).substring(to: min(sanitized.count,sanitized.count - 1))
} else {
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let sanitized = self.unformattedString(for: newString)
inputText = sanitized
}
if textField.text == inputText {
return false
}
textField.text = inputText
return false
}
func unformattedString(for string: String) -> String {
return self.stringByRemovingCharactersFromSet(string: string as NSString)
}
fileprivate func stringByRemovingCharactersFromSet(string: NSString, cs: CharacterSet = CharacterSet(charactersIn: "0123456789").inverted) -> String {
var range: NSRange = string.rangeOfCharacter(from: cs)
if (range.location != NSNotFound) {
var newString = string.substring(with: NSMakeRange(0, range.location))
var lastPosition: Int = NSMaxRange(range)
while (lastPosition < string.length) {
range = string.rangeOfCharacter(from: cs, options: NSString.CompareOptions.init(rawValue: 0), range: NSMakeRange(lastPosition, string.length - lastPosition))
if (range.location == NSNotFound) {
break;
}
if (range.location != lastPosition) {
newString.append(string.substring(with: NSMakeRange(lastPosition, range.location - lastPosition)))
}
lastPosition = NSMaxRange(range);
}
if (lastPosition != string.length) {
newString.append(string.substring(with: NSMakeRange(lastPosition, string.length - lastPosition)))
}
return newString as String
} else {
return string as String
}
}
}
看这份代码 加了注释
import UIKit
enum LYMCardValidationState: Int {
case valid = 0 ,inValid = 1, complete = 2
}
class LYMCardDateTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
self.delegate = self
self.defaultColor = .black;
self.errorColor = .red
self.validText = true
}
override init(frame: CGRect) {
super.init(frame: frame)
self.delegate = self
self.defaultColor = .black;
self.errorColor = .red
self.validText = true
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override var text: String? {
didSet (newVale){
self.rawExpiration = self.text ?? ""
let state = self.validationStateForField()
self.validText = true;
switch (state) {
case .inValid:
self.validText = false;
break
default:
break
}
let attributed = NSAttributedString(string: self.rawExpiration, attributes: defaultTextAttributes)
attributedText = attributed
}
}
var rawExpiration: String {
set {
let sanitizedExpiration = self.validateNumberStringReturnNewString(newValue)
expirationMonth = (sanitizedExpiration as NSString).substringTo(index: 2)
let temp = (sanitizedExpiration as NSString).substring(from: min(2, sanitizedExpiration.count))
expirationYear = (temp as NSString).substringTo(index: 2)
}
get {
var array: [String] = []
if expirationMonth != nil && expirationMonth != "" {
array.append(expirationMonth!)
}
if self.validationMonthState(expirationMonth: expirationMonth ?? "") == .valid {
array.append(expirationYear!)
}
return array.joined(separator: "/")
}
}
var expirationMonth: String? {
didSet {
var sanitizedExpiration = self.validateNumberStringReturnNewString(((expirationMonth ?? "") as NSString) as String)
if sanitizedExpiration.count == 1 && !(sanitizedExpiration == "0") && !(sanitizedExpiration == "1") {
sanitizedExpiration = "0" + sanitizedExpiration
}
expirationMonth = (sanitizedExpiration as NSString).substring(to: min(2, sanitizedExpiration.count))
}
}
var expirationYear: String? {
didSet {
let temp = (self.validateNumberStringReturnNewString(expirationYear ?? "")
as NSString)
expirationYear = temp.substring(to: min(2, temp.length))
}
}
var errorColor: UIColor? {
didSet {
self.updateColor()
}
}
var defaultColor: UIColor? {
didSet {
self.updateColor()
}
}
var validText: Bool? {
didSet {
self.updateColor()
}
}
}
extension LYMCardDateTextField {
func updateColor() {
textColor = validText ?? true ? defaultColor : errorColor
}
func validationMonthState(expirationMonth: String) -> LYMCardValidationState {
let sanitizedExpiration = expirationMonth
if !self.validateNumberString(expirationMonth) {
return .inValid
}
switch sanitizedExpiration.count {
case 0:
return .complete
case 1:
return (((sanitizedExpiration == "0") || (sanitizedExpiration == "1")) ? .complete : .valid)
case 2:
return ((0 < Int(sanitizedExpiration) ?? 0 && Int(sanitizedExpiration) ?? 0 <= 12) ? .valid : .inValid)
default:
return .inValid
}
}
func validationStateForField() -> LYMCardValidationState {
let monthState = self.validationMonthState(expirationMonth: expirationMonth ?? "")
let yearState = self.validationState(forYear: expirationYear, inMonth: expirationMonth)
if monthState == .valid && yearState == .valid {
return .valid
} else if monthState == .inValid || yearState == .inValid {
return .inValid
} else {
return .complete
}
}
func validationState (
forYear expirationYear: String?,inMonth expirationMonth: String?) -> LYMCardValidationState {
return self.validationState(
forExpirationYear: expirationYear,
inMonth: expirationMonth,
inCurrentYear: self.currentYear(),
currentMonth: self.currentMonth())
}
func currentYear() -> Int {
let calendar = Calendar(identifier: .gregorian)
let dateComponents = calendar.component(.year, from: Date())
return (dateComponents) % 100
}
func currentMonth() -> Int {
let calendar = Calendar(identifier: .gregorian)
let dateComponents = calendar.component(.month, from: Date())
return dateComponents
}
func validationState(forExpirationYear expirationYear: String?, inMonth expirationMonth: String?, inCurrentYear currentYear: Int, currentMonth: Int) -> LYMCardValidationState {
let moddedYear = currentYear % 100
if !self.validateNumberString(expirationMonth ?? "") || !self.validateNumberString(expirationYear ?? "") {
return .inValid
}
let sanitizedMonth = self.validateNumberStringReturnNewString( expirationMonth ?? "")
let sanitizedYear = self.validateNumberStringReturnNewString(expirationYear ?? "")
switch sanitizedYear.count {
case 0, 1:
return .complete
case 2:
if validationMonthState(expirationMonth: sanitizedMonth) == .inValid {
return .inValid
} else {
if Int(sanitizedYear) == moddedYear {
return (Int(sanitizedMonth) ?? 0) >= currentMonth ? .valid : .inValid
} else {
return (Int(sanitizedYear) ?? 0) > moddedYear ? .valid : .inValid
}
}
default:
return .inValid
}
}
func validateNumberString(_ string: String) -> Bool {
let dateRegEx = "^\\d{0,}$"
let dateTest = NSPredicate(format: "SELF MATCHES %@", dateRegEx)
return dateTest.evaluate(with: string)
}
func validateNumberStringReturnNewString(_ string: String) -> String {
if self.validateNumberString(string) {
return string
}else {
return ""
}
}
}
extension LYMCardDateTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var inputText: String?
var newString = ""
let deleting = range.location == textField.text!.count - 1 && range.length == 1 && (string == "")
if deleting {
newString = (textField.text! as NSString).replacingOccurrences(of: "/", with: "")
newString = (newString as NSString).substringTo(index: newString.count - 1)
}else {
newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
newString = (newString as NSString).replacingOccurrences(of: "/", with: "")
}
let result = self.validateNumberString(newString)
if result {
inputText = newString
}else {
inputText = textField.text
}
if textField.text == inputText {
return false
}
textField.text = inputText
return false
}
}
extension NSString {
func substringTo(index: Int) -> String {
return self.substring(to: min(index, self.length))
}
}