Formatting Phone number in Swift
Masked number typing
/// mask example: `+X (XXX) XXX-XXXX`func format(with mask: String, phone: String) -> String { let numbers = phone.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression) var result = "" var index = numbers.startIndex // numbers iterator // iterate over the mask characters until the iterator of numbers ends for ch in mask where index < numbers.endIndex { if ch == "X" { // mask requires a number in this place, so take the next one result.append(numbers[index]) // move numbers iterator to the next index index = numbers.index(after: index) } else { result.append(ch) // just append a mask character } } return result}
Call the above function from the UITextField delegate method:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { guard let text = textField.text else { return false } let newString = (text as NSString).replacingCharacters(in: range, with: string) textField.text = format(with: "+X (XXX) XXX-XXXX", phone: newString) return false}
So, that is work better.
"" => """0" => "+0""412" => "+4 (12""12345678901" => "+1 (234) 567-8901""a1_b2-c3=d4 e5&f6|g7h8" => "+1 (234) 567-8"
Really simple solution:
extension String { func applyPatternOnNumbers(pattern: String, replacementCharacter: Character) -> String { var pureNumber = self.replacingOccurrences( of: "[^0-9]", with: "", options: .regularExpression) for index in 0 ..< pattern.count { guard index < pureNumber.count else { return pureNumber } let stringIndex = String.Index(utf16Offset: index, in: pattern) let patternCharacter = pattern[stringIndex] guard patternCharacter != replacementCharacter else { continue } pureNumber.insert(patternCharacter, at: stringIndex) } return pureNumber }}
Usage:
guard let text = textField.text else { return }textField.text = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")
Swift 3 & 4
This solution removes any non-numeric characters before applying formatting. It returns nil
if the source phone number cannot be formatted according to assumptions.
Swift 4
The Swift 4 solution accounts for the deprecation of CharacterView and Sting becoming a collection of characters as the CharacterView is.
import Foundationfunc format(phoneNumber sourcePhoneNumber: String) -> String? { // Remove any character that is not a number let numbersOnly = sourcePhoneNumber.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() let length = numbersOnly.count let hasLeadingOne = numbersOnly.hasPrefix("1") // Check for supported phone number length guard length == 7 || (length == 10 && !hasLeadingOne) || (length == 11 && hasLeadingOne) else { return nil } let hasAreaCode = (length >= 10) var sourceIndex = 0 // Leading 1 var leadingOne = "" if hasLeadingOne { leadingOne = "1 " sourceIndex += 1 } // Area code var areaCode = "" if hasAreaCode { let areaCodeLength = 3 guard let areaCodeSubstring = numbersOnly.substring(start: sourceIndex, offsetBy: areaCodeLength) else { return nil } areaCode = String(format: "(%@) ", areaCodeSubstring) sourceIndex += areaCodeLength } // Prefix, 3 characters let prefixLength = 3 guard let prefix = numbersOnly.substring(start: sourceIndex, offsetBy: prefixLength) else { return nil } sourceIndex += prefixLength // Suffix, 4 characters let suffixLength = 4 guard let suffix = numbersOnly.substring(start: sourceIndex, offsetBy: suffixLength) else { return nil } return leadingOne + areaCode + prefix + "-" + suffix}extension String { /// This method makes it easier extract a substring by character index where a character is viewed as a human-readable character (grapheme cluster). internal func substring(start: Int, offsetBy: Int) -> String? { guard let substringStartIndex = self.index(startIndex, offsetBy: start, limitedBy: endIndex) else { return nil } guard let substringEndIndex = self.index(startIndex, offsetBy: start + offsetBy, limitedBy: endIndex) else { return nil } return String(self[substringStartIndex ..< substringEndIndex]) }}
Swift 3
import Foundationfunc format(phoneNumber sourcePhoneNumber: String) -> String? { // Remove any character that is not a number let numbersOnly = sourcePhoneNumber.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() let length = numbersOnly.characters.count let hasLeadingOne = numbersOnly.hasPrefix("1") // Check for supported phone number length guard length == 7 || (length == 10 && !hasLeadingOne) || (length == 11 && hasLeadingOne) else { return nil } let hasAreaCode = (length >= 10) var sourceIndex = 0 // Leading 1 var leadingOne = "" if hasLeadingOne { leadingOne = "1 " sourceIndex += 1 } // Area code var areaCode = "" if hasAreaCode { let areaCodeLength = 3 guard let areaCodeSubstring = numbersOnly.characters.substring(start: sourceIndex, offsetBy: areaCodeLength) else { return nil } areaCode = String(format: "(%@) ", areaCodeSubstring) sourceIndex += areaCodeLength } // Prefix, 3 characters let prefixLength = 3 guard let prefix = numbersOnly.characters.substring(start: sourceIndex, offsetBy: prefixLength) else { return nil } sourceIndex += prefixLength // Suffix, 4 characters let suffixLength = 4 guard let suffix = numbersOnly.characters.substring(start: sourceIndex, offsetBy: suffixLength) else { return nil } return leadingOne + areaCode + prefix + "-" + suffix}extension String.CharacterView { /// This method makes it easier extract a substring by character index where a character is viewed as a human-readable character (grapheme cluster). internal func substring(start: Int, offsetBy: Int) -> String? { guard let substringStartIndex = self.index(startIndex, offsetBy: start, limitedBy: endIndex) else { return nil } guard let substringEndIndex = self.index(startIndex, offsetBy: start + offsetBy, limitedBy: endIndex) else { return nil } return String(self[substringStartIndex ..< substringEndIndex]) }}
Example
func testFormat(sourcePhoneNumber: String) -> String { if let formattedPhoneNumber = format(phoneNumber: sourcePhoneNumber) { return "'\(sourcePhoneNumber)' => '\(formattedPhoneNumber)'" } else { return "'\(sourcePhoneNumber)' => nil" }}print(testFormat(sourcePhoneNumber: "1 800 222 3333"))print(testFormat(sourcePhoneNumber: "18002223333"))print(testFormat(sourcePhoneNumber: "8002223333"))print(testFormat(sourcePhoneNumber: "2223333"))print(testFormat(sourcePhoneNumber: "18002223333444"))print(testFormat(sourcePhoneNumber: "Letters8002223333"))print(testFormat(sourcePhoneNumber: "1112223333"))
Example Output
'1 800 222 3333' => '1 (800) 222-3333''18002223333' => '1 (800) 222-3333''8002223333' => '(800) 222-3333''2223333' => '222-3333''18002223333444' => nil'Letters8002223333' => '(800) 222-3333''1112223333' => nil