Skip to content

Instantly share code, notes, and snippets.

@ksmandersen
Created October 11, 2022 08:53
Show Gist options
  • Save ksmandersen/9dd7942a9a7c26a1f61dda6583aef24d to your computer and use it in GitHub Desktop.
Save ksmandersen/9dd7942a9a7c26a1f61dda6583aef24d to your computer and use it in GitHub Desktop.
How to format a credit card number with an (NS)Formatter
class CreditCardFormatter: Formatter {
override func string(for obj: Any?) -> String? {
guard let input = obj as? String else {
return nil
}
return formattedCardNumber(input: input)
}
override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
obj?.pointee = string as AnyObject?
return true
}
private func formattedCardNumber(input: String?) -> String? {
guard let input = input else { return nil }
let stripped = input.replacingOccurrences(of: " ", with: "")
// Guess which mask we need to use based on what the user has
// typed so far, without any spacing or formatting.
let mask = guessFormattingMask(for: stripped)
var result = ""
var index = stripped.startIndex
// Format the stripped input based on the guessed mask
for mchar in mask where index < stripped.endIndex {
if mchar == "#" {
result.append(stripped[index])
index = stripped.index(after: index)
} else {
if stripped[index] != mchar {
result.append(mchar)
} else {
result.append(stripped[index])
index = stripped.index(after: index)
}
}
}
// print("Result: \(result)")
// print("Mask: \(mask)")
// print("input: \(input.count), result: \(result.count), mask: \(mask.count), stripped: \(stripped.count)")
// If we exceeded the mask just append additional characters to the end
// of the result
if input.count > mask.count && result.endIndex < input.endIndex {
result.append(String(input[result.endIndex..<input.endIndex]))
}
return result
}
func guessFormattingMask(for string: String) -> String {
let defaultMask = "#### #### #### ####"
switch string.first {
case "3":
if string.starts(with: "34") || string.starts(with: "37") {
return "#### ###### #####"
} else if string.starts(with: "36") {
return "#### ##### ####"
} else {
return defaultMask
}
case "4": return string.count == 13 ? "#### ##### ####" : defaultMask
default: return defaultMask
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment