Formatting Phone number in Swift Formatting Phone number in Swift ios ios

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|g7h8" => "+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