59

I'm formatting my textfiled text once the user start typing the phone number into this format type 0 (555) 444 66 77 and it is working fine but once I get the number from the server I get it like this 05554446677 So please could you tell me how I can edit it in the same format once I get it fro the server?

My code once I start typing:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    if textField == phoneNumberTextField{
        var newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
        var components = newString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet)

        var decimalString = "".join(components) as NSString
        var length = decimalString.length
        var hasLeadingOne = length > 0 && decimalString.characterAtIndex(0) == (1 as unichar)

        if length == 0 || (length > 11 && !hasLeadingOne) || length > 12{
            var newLength = (textField.text as NSString).length + (string as NSString).length - range.length as Int

            return (newLength > 11) ? false : true
        }
        var index = 0 as Int
        var formattedString = NSMutableString()

        if hasLeadingOne{
            formattedString.appendString("1 ")
            index += 1
        }

        if (length - index) > 1{
            var zeroNumber = decimalString.substringWithRange(NSMakeRange(index, 1))
            formattedString.appendFormat("%@ ", zeroNumber)
            index += 1
        }
        if (length - index) > 3{
            var areaCode = decimalString.substringWithRange(NSMakeRange(index, 3))
            formattedString.appendFormat("(%@) ", areaCode)
            index += 3
        }
        if (length - index) > 3{
            var prefix = decimalString.substringWithRange(NSMakeRange(index, 3))
            formattedString.appendFormat("%@ ", prefix)
            index += 3
        }
        if (length - index) > 3{
            var prefix = decimalString.substringWithRange(NSMakeRange(index, 2))
            formattedString.appendFormat("%@ ", prefix)
            index += 2
        }

        var remainder = decimalString.substringFromIndex(index)
        formattedString.appendString(remainder)
        textField.text = formattedString as String
        return false
    }else{
        return true
    }
}
Adrian
  • 16,233
  • 18
  • 112
  • 180
CAN
  • 1,677
  • 4
  • 19
  • 28
  • 5
    Phone numbers are incredibly complex. Any assumptions you might have about their format are purely local, and surely have tons of exceptions and edge cases to them. Use a library instead, that has codified all of these exceptions to work internationally. E.g.: https://github.com/marmelroy/PhoneNumberKit – Alexander May 27 '19 at 16:04

15 Answers15

154

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 works better.

"" => ""
"0" => "+0"
"412" => "+4 (12"
"12345678901" => "+1 (234) 567-8901"
"a1_b2-c3=d4 e5&f6|g7h8" => "+1 (234) 567-8"
Jake Chasan
  • 6,290
  • 9
  • 44
  • 90
Roman Filippov
  • 2,289
  • 2
  • 11
  • 17
  • 1
    This will brake iOS feature with phone number suggestions unfortunately. – atereshkov Nov 12 '19 at 11:09
  • This is an elegant solution. When i wanted to format phonenumbers differently based on number of digits, i just used a different mask based on the phone string count. – anoo_radha Dec 18 '19 at 19:13
  • Works for Swift 4.2 – J A S K I E R Dec 26 '19 at 07:27
  • Just to add that if you create your own mask such as `(XXX) XXX-XXXX`, it will work then as well. Also, if you need the plain string back just create a plain mask like `XXXXXXXXXX` and it will convert the formatted phone number back to normal string. – nr5 Jan 04 '20 at 17:51
  • func removeNumberFormat(number: String) -> String { let digits = CharacterSet.decimalDigits var text = "" for char in number.unicodeScalars { if digits.contains(char) { text.append(char.description) } } return text } – Ahemadabbas Vagh May 09 '20 at 13:09
  • 1
    To allow this to work with phone number suggestions, just change the last return = false to: `return string == " "`. iOS auto-completion always adds an extra space first, then the new value in front of the space. Fortunately, the `format() -> String` method should remove this extra space for you. – jbcaveman Feb 23 '21 at 17:27
76

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: "#")
Alexander
  • 9,074
  • 2
  • 15
  • 13
44

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 Foundation

func 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 Foundation

func 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
Mobile Dan
  • 6,444
  • 1
  • 44
  • 44
  • 2
    This should be the correct solution. Provides more flexibility and can be placed in its own class and used throughout the application. – TuplingD Mar 07 '18 at 13:00
  • I believe this will crash for the input 1112223333. Although it is not a valid phone number, still possible for the user to enter it. – sanch Nov 02 '19 at 04:56
  • 1
    Thanks @sanch for pointing out the example of a 10 digit number starting with a "1". Such an input will make it past the guard under "// Check for supported phone number length". I had not considered a phone number input like that. I tested the Swift 4 code and thankfully it returns a nil due to the guard statement under the comment "// Suffix, 4 characters". – Mobile Dan Nov 04 '19 at 23:39
  • 1
    @sanch, I improved the code to better handle a 10 number input that starts with a '1'. Now the guard under "// Check for supported phone number length" explicitly rejects an input like this. I added "1112223333" to the examples and to my own unit tests for this code. Thank you. – Mobile Dan Nov 04 '19 at 23:53
16

Manipulations with characters in String are not very straightforward. You need following:

Swift 2.1

let s = "05554446677"
let s2 = String(format: "%@ (%@) %@ %@ %@", s.substringToIndex(s.startIndex.advancedBy(1)),
    s.substringWithRange(s.startIndex.advancedBy(1) ... s.startIndex.advancedBy(3)),
    s.substringWithRange(s.startIndex.advancedBy(4) ... s.startIndex.advancedBy(6)),
    s.substringWithRange(s.startIndex.advancedBy(7) ... s.startIndex.advancedBy(8)),
    s.substringWithRange(s.startIndex.advancedBy(9) ... s.startIndex.advancedBy(10))
)

Swift 2.0

let s = "05554446677"
let s2 = String(format: "%@ (%@) %@ %@ %@", s.substringToIndex(advance(s.startIndex, 1)),
    s.substringWithRange(advance(s.startIndex, 1) ... advance(s.startIndex, 3)),
    s.substringWithRange(advance(s.startIndex, 4) ... advance(s.startIndex, 6)),
    s.substringWithRange(advance(s.startIndex, 7) ... advance(s.startIndex, 8)),
    s.substringWithRange(advance(s.startIndex, 9) ... advance(s.startIndex, 10))
)

Code will print 0 (555) 444 66 77

Avt
  • 16,927
  • 4
  • 52
  • 72
5

You can use this library https://github.com/luximetr/AnyFormatKit

Example

let phoneFormatter = DefaultTextFormatter(textPattern: "### (###) ###-##-##")
phoneFormatter.format("+123456789012") // +12 (345) 678-90-12

Very simple to use.

iOS Developer
  • 71
  • 2
  • 2
  • 1
    I don't know why but this doesn't work for me. I set as textInputController.textInput my UITextField. I also make its class TexInputField in Identity inspector in storyboard and make suitable casting. Filed isn't live formatted – Michał Ziobro Mar 05 '18 at 11:47
5

Swift 4

Create this function and call on text field event Editing Changed

private func formatPhone(_ number: String) -> String {
    let cleanNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
    let format: [Character] = ["X", "X", "X", "-", "X", "X", "X", "-", "X", "X", "X", "X"]

    var result = ""
    var index = cleanNumber.startIndex
    for ch in format {
        if index == cleanNumber.endIndex {
            break
        }
        if ch == "X" {
            result.append(cleanNumber[index])
            index = cleanNumber.index(after: index)
        } else {
            result.append(ch)
        }
    }
    return result
}
Chhaileng
  • 2,428
  • 1
  • 27
  • 24
5

Swift 5.1 Update on Дарія Прокопович great solution

extension String {

    func applyPatternOnNumbers(pattern: String, replacmentCharacter: 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: self)
            let patternCharacter = pattern[stringIndex]
            guard patternCharacter != replacmentCharacter else { continue }
            pureNumber.insert(patternCharacter, at: stringIndex)
        }
        return pureNumber
    }
}

Usage:

let formattedText = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")
Mark Wilson
  • 269
  • 4
  • 4
  • 1
    how to use this inside a TextField with a Binding ? I'm not able to set the formatter with the binding value. Example: TextField("",self.$phoneNumber. applyPatternOnNumbers(...)) – Daniel Belém Duarte Jun 28 '20 at 16:22
4

Swift 3 but should also be translatable to Swift 4

  1. ErrorHandling

    enum PhoneNumberFormattingError: Error {
        case wrongCharactersInPhoneNumber
        case phoneNumberLongerThanPatternAllowes
    }
    
  2. Create Patterns

    enum PhoneNumberFormattingPatterns: String {
        case mobile = "+xx (yxx) xxxxxxxxxxx"
        case home = "+xx (yxxx) xxxx-xxx"
    }
    
  3. Insert Function

    /**
         Formats a phone-number to correct format
         - Parameter pattern: The pattern to format the phone-number.
         - Example:
            - x: Says that this should be a digit.
            - y: Says that this digit cannot be a "0".
            - The length of the pattern restricts also the length of allowed phone-number digits.
                - phone-number: "+4306641234567"
                - pattern: "+xx (yxx) xxxxxxxxxxx"
                - result: "+43 (664) 1234567"
    
         - Throws:
            - PhoneNumberFormattingError
                - wrongCharactersInPhoneNumber: if phone-number contains other characters than digits.
                - phoneNumberLongerThanPatternAllowes: if phone-number is longer than pattern allows.
         - Returns:
            - The formatted phone-number due to the pattern.
         */
    extension String {
        func vpToFormattedPhoneNumber(withPattern pattern: PhoneNumberFormattingPatterns) throws -> String {
            let phoneNumber = self.replacingOccurrences(of: "+", with: "")
            var retVal: String = ""
            var index = 0
            for char in pattern.rawValue.lowercased().characters {
                guard index < phoneNumber.characters.count else {
                    return retVal
                }
    
                if char == "x" {
                    let charIndex = phoneNumber.index(phoneNumber.startIndex, offsetBy: index)
                    let phoneChar = phoneNumber[charIndex]
                    guard "0"..."9" ~= phoneChar else {
                        throw PhoneNumberFormattingError.wrongCharactersInPhoneNumber
                    }
                    retVal.append(phoneChar)
                    index += 1
                } else if char == "y" {
                    var charIndex = phoneNumber.index(phoneNumber.startIndex, offsetBy: index)
                    var indexTemp = 1
                    while phoneNumber[charIndex] == "0" {
                        charIndex = phoneNumber.index(phoneNumber.startIndex, offsetBy: index + indexTemp)
                        indexTemp += 1
                    }
    
                    let phoneChar = phoneNumber[charIndex]
                    guard "0"..."9" ~= phoneChar else {
                        throw PhoneNumberFormattingError.wrongCharactersInPhoneNumber
                    }
                    retVal.append(phoneChar)
                    index += indexTemp
                } else {
                    retVal.append(char)
                }
            }
    
            if phoneNumber.endIndex > phoneNumber.index(phoneNumber.startIndex, offsetBy: index) {
                throw PhoneNumberFormattingError.phoneNumberLongerThanPatternAllowes
            }
    
            return retVal
        }
    }
    
  4. Usage

    let phoneNumber = "+4306641234567"
    let phoneNumber2 = "4343211234567"
    
    do {
        print(try phoneNumber.vpToFormattedPhoneNumber(withPattern: .mobile))
        print(try phoneNumber2.vpToFormattedPhoneNumber(withPattern: .home))
    } catch let error as PhoneNumberFormattingError {
        switch error {
        case .wrongCharactersInPhoneNumber:
            print("wrong characters in phone number")
        case .phoneNumberLongerThanPatternAllowes:
            print("too long phone number")
        default:
            print("unknown error")
        }
    } catch {
        print("something other went wrong")
    }
    
    // output: +43 (664) 1234567
    // output: +43 (4321) 1234-567
    
2

There are a number of good answers here but I took a completely different approach and thought I'd share in case it helps.

To start I broke up the formatting steps and components into their own separate responsibilities.

Phone number format can generally be broken down into local, domestic or international format types that vary by string length.

I defined the types:

/// Defines the three different types of formatting phone numbers use
///
/// - local: Numbers used locally.
/// - domestic: Numbers used locally including area codes.
/// - international: Numbers used internationally with country codes.
public enum PhoneFormatType {
    case local
    case domestic
    case international
}

Then defined the separators available to format a phone number string:

// Defines separators that are available for use in formatting
// phone number strings.
public enum PhoneFormatSeparator {
    case hyphen
    case plus
    case space
    case parenthesisLH
    case parenthesisRH
    case slash
    case backslash
    case pipe
    case asterisk

    public var value: String {
        switch self {
        case .hyphen: return "-"
        case .plus: return "+"
        case .space: return " "
        case .parenthesisLH: return "("
        case .parenthesisRH: return ")"
        case .slash: return "/"
        case .backslash: return "\\"
        case .pipe: return "|"
        case .asterisk: return "*"
        }
    }
}

Next I defined formatting rules that specify the index (in a phone number string) where the separators like +,-,etc are inserted.

// defines the separators that should be inserted in a phone number string
// and the indexes where they should be applied
public protocol PhoneNumberFormatRule {

    // the index in a phone number where this separator should be applied
    var index: Int { get set }

    // the priority in which this rule should be applied. Sorted in inverse, 0 is highest priority, higher numbers are lower priority
    var priority: Int { get set }

    // the separator to use at this index
    var separator: PhoneFormatSeparator { get set }
}

/// Default implementation of PhoneNumberFormatRule
open class PNFormatRule: PhoneNumberFormatRule {
    public var index: Int
    public var priority: Int
    public var separator: PhoneFormatSeparator

    public init(_ index: Int, separator: PhoneFormatSeparator, priority: Int = 0) {
        self.index = index
        self.separator = separator
        self.priority = priority
    }
}

With these defined, I created rulesets that associate rules with a given format type.

/// Defines the rule sets associated with a given phone number type.
/// e.g. international/domestic/local
public protocol PhoneFormatRuleset {

    /// The type of phone number formatting to which these rules apply
    var type: PhoneFormatType { get set }

    /// A collection of rules to apply for this phone number type.
    var rules: [PhoneNumberFormatRule] { get set }

    /// The maximum length a number using this format ruleset should be. (Inclusive)
    var maxLength: Int { get set }
}

With everything defined this way, you can setup rulesets quickly to suit whatever format you need.

Here's an example of a ruleset that defines 3 rules for a hyphen formatted phone number string typically used in the US:

    // Formats phone numbers:
    //  .local: 123-4567
    //  .domestic: 123-456-7890
    //  .international: +1 234-567-8901
    static func usHyphen() -> [PhoneFormatRuleset] {
        return [
            PNFormatRuleset(.local, rules: [
                PNFormatRule(3, separator: .hyphen)
                ], maxLength: 7),
            PNFormatRuleset(.domestic, rules: [
                PNFormatRule(3, separator: .hyphen),
                PNFormatRule(6, separator: .hyphen)
                ], maxLength: 10),
            PNFormatRuleset(.international, rules: [
                PNFormatRule(0, separator: .plus),
                PNFormatRule(1, separator: .space),
                PNFormatRule(4, separator: .hyphen),
                PNFormatRule(7, separator: .hyphen)
                ], maxLength: 11)
        ]
    }

The (not so) heavy lifting of the formatting logic happens here:

// formats a string using the format rule provided at initialization
public func format(number: String) -> String {

    // strip non numeric characters
    let n = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()

    // bail if we have an empty string, or if no ruleset is defined to handle formatting
    guard n.count > 0, let type = type(for: n.count), let ruleset = ruleset(for: type) else {
        return n
    }

    // this is the string we'll return
    var formatted = ""

    // enumerate the numeric string
    for (i,character) in n.enumerated() {

        // bail if user entered more numbers than allowed for our formatting ruleset
        guard i <= ruleset.maxLength else {
            break
        }

        // if there is a separator defined to be inserted at this index then add it to the formatted string
        if let separator = ruleset.separator(for: i) {
            formatted+=separator
        }

        // now append the character
        formatted+="\(character)"
    }

    return formatted
} 

I've created a framework with a sample project you can look through here: https://github.com/appteur/phoneformat

Here is how it works as you type:

Demo Gif

I also set it up so you can just import it with cocoapods.

pod 'SwiftPhoneFormat', '1.0.0'

Then use it:

import SwiftPhoneFormat

var formatter = PhoneFormatter(rulesets: PNFormatRuleset.usParethesis())
let formatted = formatter.format(number: numberString)
digitalHound
  • 4,384
  • 27
  • 27
1

This is the extension which will full fill your requirement:

 extension String {
 func convertToInternationalFormat() -> String {
    let isMoreThanTenDigit = self.count > 10
    _ = self.startIndex
    var newstr = ""
    if isMoreThanTenDigit {
        newstr = "\(self.dropFirst(self.count - 10))"
    }
    else if self.count == 10{
        newstr = "\(self)"
    }
    else {
        return "number has only \(self.count) digits"
    }
    if  newstr.count == 10 {
        let internationalString = "(\(newstr.dropLast(7))) \(newstr.dropLast(4).dropFirst(3)) \(newstr.dropFirst(6).dropLast(2)) \(newstr.dropFirst(8))"
        newstr = internationalString
    }
    return newstr
 }
 }

INPUT :
var str1 = "9253248954"
var str2 = "+19253248954"
var str3 = "19253248954"

OUTPUT :
str1.convertToInternationalFormat() // "(925) 324 89 54"
str2.convertToInternationalFormat() // "(925) 324 89 54"
str3.convertToInternationalFormat() // "(925) 324 89 54"
Prabhat Kasera
  • 1,129
  • 11
  • 28
0

If you rather to do it without using a library. Here is a link to the best example or you can use the code below.

https://ivrodriguez.com/format-phone-numbers-in-swift/

A simple code snippet to format 10 digit phone numbers in Swift 5.0, instead of including a big library, just implement a delegate function and a formatting function:

  • The UITextFieldDelegate function
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    var fullString = textField.text ?? ""
    fullString.append(string)
    if range.length == 1 {
        textField.text = format(phoneNumber: fullString, shouldRemoveLastDigit: true)
    } else {
        textField.text = format(phoneNumber: fullString)
    }
    return false
}
  • The formatting function:
func format(phoneNumber: String, shouldRemoveLastDigit: Bool = false) -> String {
    guard !phoneNumber.isEmpty else { return "" }
    guard let regex = try? NSRegularExpression(pattern: "[\\s-\\(\\)]", options: .caseInsensitive) else { return "" }
    let r = NSString(string: phoneNumber).range(of: phoneNumber)
    var number = regex.stringByReplacingMatches(in: phoneNumber, options: .init(rawValue: 0), range: r, withTemplate: "")

    if number.count > 10 {
        let tenthDigitIndex = number.index(number.startIndex, offsetBy: 10)
        number = String(number[number.startIndex..<tenthDigitIndex])
    }

    if shouldRemoveLastDigit {
        let end = number.index(number.startIndex, offsetBy: number.count-1)
        number = String(number[number.startIndex..<end])
    }

    if number.count < 7 {
        let end = number.index(number.startIndex, offsetBy: number.count)
        let range = number.startIndex..<end
        number = number.replacingOccurrences(of: "(\\d{3})(\\d+)", with: "($1) $2", options: .regularExpression, range: range)

    } else {
        let end = number.index(number.startIndex, offsetBy: number.count)
        let range = number.startIndex..<end
        number = number.replacingOccurrences(of: "(\\d{3})(\\d{3})(\\d+)", with: "($1) $2-$3", options: .regularExpression, range: range)
    }

    return number
}
Wurl Link
  • 29
  • 5
  • This a good solution but it has an issue if you have some rules inside TextField.Target function it will prevent to go to target (textFieldChanged()) function. – Ali Qaderi Oct 14 '20 at 05:36
0

SwiftUI code for mobile number formatting textfield

struct MobileNumberTextFieldContainer: UIViewRepresentable {
private var placeholder : String
private var text : Binding<String>

init(_ placeholder:String, text:Binding<String>) {
    self.placeholder = placeholder
    self.text = text
}

func makeCoordinator() -> MobileNumberTextFieldContainer.Coordinator {
    Coordinator(self)
}

func makeUIView(context: UIViewRepresentableContext<MobileNumberTextFieldContainer>) -> UITextField {

    let innertTextField = UITextField(frame: .zero)
    innertTextField.placeholder = placeholder
    innertTextField.text = text.wrappedValue
    innertTextField.delegate = context.coordinator

    context.coordinator.setup(innertTextField)

    return innertTextField
}

func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<MobileNumberTextFieldContainer>) {
    uiView.text = self.text.wrappedValue
}

class Coordinator: NSObject, UITextFieldDelegate {
    var parent: MobileNumberTextFieldContainer

    init(_ textFieldContainer: MobileNumberTextFieldContainer) {
        self.parent = textFieldContainer
    }

    func setup(_ textField:UITextField) {
        textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
    }

    @objc func textFieldDidChange(_ textField: UITextField) {
        var isCursorLast = false
        var cursorPosition = 0
        
        let textLenght =  textField.text?.count ?? 0
        if let selectedRange = textField.selectedTextRange {
            
            cursorPosition = textField.offset(from: textField.beginningOfDocument, to: selectedRange.start)
            
            print("\(cursorPosition) lengh = \(textLenght)")
            
            if cursorPosition < textLenght {
                isCursorLast = true
            }
        }
        
        textField.text = textField.text?.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacementCharacter: "#")
        //textField.text = textField.text ?? "".format(phoneNumber: textField.text ?? "")
        self.parent.text.wrappedValue = textField.text ?? ""
        
        if isCursorLast {
            isCursorLast = false
            
            let arbitraryValue: Int = cursorPosition
            if let newPosition = textField.position(from: textField.beginningOfDocument, offset: arbitraryValue) {

                textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
            }
        }
    }
}

}

I have use the same formatting function which is @Mark Wilson used And simply we can add this in our view

MobileNumberTextFieldContainer("Phone Number", text: $phoneNumber)
Akhtar
  • 3,172
  • 5
  • 19
  • 21
0

SwiftUI

My answer tweaks and builds on Mobile Dan's answer and adapts it for a SwiftUI TextField. If formatting fails or it's less than 10 numbers, it will return the unformatted string. This works with the phone number suggestion feature, assuming a one digit country code. Should be easy to adapt for multi-digit country codes.

TextField("Phone", text: $phoneNumber)
    .keyboardType(.numberPad)
    .textContentType(.telephoneNumber)
    .onChange(of: phoneNumber) { _ in
        phoneNumber = phoneNumber.formatPhoneNumber()
    }

String Extensions:

extension String {
    func formatPhoneNumber() -> String {
        // Remove any character that is not a number
        let numbersOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
        let length = numbersOnly.count
    
        // Check for supported phone number length
        if length > 11 {
            return String(numbersOnly.prefix(11)).formatPhoneNumber()
        } else if length < 10 {
            return numbersOnly
        }
    
        var sourceIndex = 0
    
        // Leading Number
        var leadingNumber = ""
        if length == 11, let leadChar = numbersOnly.first {
            leadingNumber = String(leadChar) + " "
            sourceIndex += 1
        }
    
        // Area code
        var areaCode = ""
        let areaCodeLength = 3
        guard let areaCodeSubstring = numbersOnly.substring(start: sourceIndex, offsetBy: areaCodeLength) else {
            return numbersOnly
        }
        areaCode = String(format: "(%@) ", areaCodeSubstring)
        sourceIndex += areaCodeLength
    
        // Prefix, 3 characters
        let prefixLength = 3
        guard let prefix = numbersOnly.substring(start: sourceIndex, offsetBy: prefixLength) else {
            return numbersOnly
        }
        sourceIndex += prefixLength
    
        // Suffix, 4 characters
        let suffixLength = 4
        guard let suffix = numbersOnly.substring(start: sourceIndex, offsetBy: suffixLength) else {
            return numbersOnly
        }
    
        return leadingNumber + areaCode + prefix + "-" + suffix
    }
}

extension String {
    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])
    }
}
Detr01tDave
  • 191
  • 1
  • 2
-1

Thank you Roman Filippov...

**

//MacOS

**

Phone Number Field for playing with... Just copy and paste into New Project...

AutoFill Leading digit(s) (ie +1)

Formatting after is Country-specific

  import SwiftUI

@main
struct phoneField: App {


var body: some Scene {
    WindowGroup {
        TJH1()
    }
  }
}


struct PhoneNumberFormat {
let leadingDigits: String
let format: String
let textFieldMaxChar: Int
}func phoneNumberFormats() -> [String: PhoneNumberFormat] {
let formats = [
    "United States": PhoneNumberFormat(leadingDigits: "+1", format: "+X (XXX) XXX-XXXX", textFieldMaxChar: 11),
    "Argentina": PhoneNumberFormat(leadingDigits: "+54", format: "+XX (X) XXXX-XXXX", textFieldMaxChar: 11),
    "Australia": PhoneNumberFormat(leadingDigits: "+61", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
    "Brazil": PhoneNumberFormat(leadingDigits: "+55", format: "+XX (XX) XXXX-XXXX", textFieldMaxChar: 12),
    "Canada": PhoneNumberFormat(leadingDigits: "+1", format: "+X (XXX) XXX-XXXX", textFieldMaxChar: 11),
    "China": PhoneNumberFormat(leadingDigits: "+86", format: "+XX (XXX) XXXX-XXXX", textFieldMaxChar: 13),
    "France": PhoneNumberFormat(leadingDigits: "+33", format: "+XX (X) XX XX XX XX", textFieldMaxChar: 11),
    "Germany": PhoneNumberFormat(leadingDigits: "+49", format: "+XX (XXX) XXXXXXX", textFieldMaxChar: 12),
    "India": PhoneNumberFormat(leadingDigits: "+91", format: "+XX XXXXX-XXXXX", textFieldMaxChar: 12),
    "Italy": PhoneNumberFormat(leadingDigits: "+39", format: "+XX (XXX) XXXXXXX", textFieldMaxChar: 12),
    "Japan": PhoneNumberFormat(leadingDigits: "+81", format: "+XX (X) XXXX-XXXX", textFieldMaxChar: 11),
    "Mexico": PhoneNumberFormat(leadingDigits: "+52", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
    "Netherlands": PhoneNumberFormat(leadingDigits: "+31", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
    "New Zealand": PhoneNumberFormat(leadingDigits: "+64", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
    "Russia": PhoneNumberFormat(leadingDigits: "+7", format: "+X (XXX) XXX-XX-XX", textFieldMaxChar: 11),
    "Saudi Arabia": PhoneNumberFormat(leadingDigits: "+966", format: "+XXX (X) XXX-XXXX", textFieldMaxChar: 11),
    "Singapore": PhoneNumberFormat(leadingDigits: "+65", format: "+XX XXXX XXXX", textFieldMaxChar: 10),
    "South Africa": PhoneNumberFormat(leadingDigits: "+27", format: "+XX (X) XXX XXXX", textFieldMaxChar: 10),
    "South Korea": PhoneNumberFormat(leadingDigits: "+82", format: "+XX (XX) XXXX-XXXX", textFieldMaxChar: 12),
    "Spain": PhoneNumberFormat(leadingDigits: "+34", format: "+XX XXX XXX XXX", textFieldMaxChar: 11),
    "Sweden": PhoneNumberFormat(leadingDigits: "+46", format: "+XX (X) XXXX-XXXX", textFieldMaxChar: 11),
    "Switzerland": PhoneNumberFormat(leadingDigits: "+41", format: "+XX (XX) XXX XX XX", textFieldMaxChar: 11),
    "Turkey": PhoneNumberFormat(leadingDigits: "+90", format: "+XX (XXX) XXX-XXXX", textFieldMaxChar: 12),
    "United Arab Emirates": PhoneNumberFormat(leadingDigits: "+971", format: "+XXX (X) XXX-XXXX", textFieldMaxChar: 11),
    "United Kingdom": PhoneNumberFormat(leadingDigits: "+44", format: "+XX (X) XXXX XXX", textFieldMaxChar: 10),
]
return formats
}



struct TJH1: View {
@State private var PNField: String = ""
@State private var selectedCountry = "United States"

func maskFormat(with mask: String, phone: String) -> String {
    let numbers = phone.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
    var result = ""
    var index = numbers.startIndex
    var maskIndex = mask.startIndex
    
    while index < numbers.endIndex && maskIndex < mask.endIndex {
        let maskChar = mask[maskIndex]
        let numberChar = numbers[index]
        
        if maskChar == "X" {
            result.append(numberChar)
            index = numbers.index(after: index)
        } else {
            result.append(maskChar)
        }
        
        maskIndex = mask.index(after: maskIndex)
    }
    
    return result
}

var countries: [String] {
    Array(phoneNumberFormats().keys).sorted()
}

var body: some View {
    GeometryReader { geometry in
        VStack {
            TextField("", text: $PNField, onEditingChanged: { begin in })
                .onChange(of: PNField) { newValue in
                    PNField = maskFormat(with: phoneNumberFormats()[selectedCountry]!.format, phone: newValue)
                    
                    if PNField.filter({ $0.isNumber }).count <= (phoneNumberFormats()[selectedCountry]!.leadingDigits.filter({ $0.isNumber }).count) {
                        let regex = try? NSRegularExpression(pattern: "[^0-9]+")
                        let matches = regex?.matches(in: newValue, options: [], range: NSRange(location: 0, length: newValue.count))
                        let count = matches?.count ?? 0
                        
                        if count != 0 {
                            PNField = phoneNumberFormats()[selectedCountry]!.leadingDigits
                        } else {
                            PNField = phoneNumberFormats()[selectedCountry]!.leadingDigits + newValue
                        }
                    }
                }
            
            Picker("Country", selection: $selectedCountry) {
                ForEach(countries, id: \.self) { country in
                    Text(country)
                        .pickerStyle(.menu)
                        .onChange(of: selectedCountry) { newValue in
                            PNField = ""
                        }
                }
            }
        }
    }
  }
}

struct TJH1_Previews: PreviewProvider {
static var previews: some View {
    TJH1()
  }
}

//iOS App

Phone Number Field for playing with... Just copy and Past into New Project...

AutoFill Leading digit(s) (ie +1)

Formatting after is Country-specific

import SwiftUI

@main
struct TOPPS_PizzaApp: App {
    
var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}


struct PhoneNumberFormat {
    let leadingDigits: String
    let format: String
    let textFieldMaxChar: Int
}

func phoneNumberFormats() -> [String: PhoneNumberFormat] {
    let formats = [
        "United States": PhoneNumberFormat(leadingDigits: "+1", format: "+X (XXX) XXX-XXXX", textFieldMaxChar: 11),
        "Argentina": PhoneNumberFormat(leadingDigits: "+54", format: "+XX (X) XXXX-XXXX", textFieldMaxChar: 11),
        "Australia": PhoneNumberFormat(leadingDigits: "+61", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
        "Brazil": PhoneNumberFormat(leadingDigits: "+55", format: "+XX (XX) XXXX-XXXX", textFieldMaxChar: 12),
        "Canada": PhoneNumberFormat(leadingDigits: "+1", format: "+X (XXX) XXX-XXXX", textFieldMaxChar: 11),
        "China": PhoneNumberFormat(leadingDigits: "+86", format: "+XX (XXX) XXXX-XXXX", textFieldMaxChar: 13),
        "France": PhoneNumberFormat(leadingDigits: "+33", format: "+XX (X) XX XX XX XX", textFieldMaxChar: 11),
        "Germany": PhoneNumberFormat(leadingDigits: "+49", format: "+XX (XXX) XXXXXXX", textFieldMaxChar: 12),
        "India": PhoneNumberFormat(leadingDigits: "+91", format: "+XX XXXXX-XXXXX", textFieldMaxChar: 12),
        "Italy": PhoneNumberFormat(leadingDigits: "+39", format: "+XX (XXX) XXXXXXX", textFieldMaxChar: 12),
        "Japan": PhoneNumberFormat(leadingDigits: "+81", format: "+XX (X) XXXX-XXXX", textFieldMaxChar: 11),
        "Mexico": PhoneNumberFormat(leadingDigits: "+52", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
        "Netherlands": PhoneNumberFormat(leadingDigits: "+31", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
        "New Zealand": PhoneNumberFormat(leadingDigits: "+64", format: "+XX (X) XXXX XXXX", textFieldMaxChar: 11),
        "Russia": PhoneNumberFormat(leadingDigits: "+7", format: "+X (XXX) XXX-XX-XX", textFieldMaxChar: 11),
        "Saudi Arabia": PhoneNumberFormat(leadingDigits: "+966", format: "+XXX (X) XXX-XXXX", textFieldMaxChar: 11),
        "Singapore": PhoneNumberFormat(leadingDigits: "+65", format: "+XX XXXX XXXX", textFieldMaxChar: 10),
        "South Africa": PhoneNumberFormat(leadingDigits: "+27", format: "+XX (X) XXX XXXX", textFieldMaxChar: 10),
        "South Korea": PhoneNumberFormat(leadingDigits: "+82", format: "+XX (XX) XXXX-XXXX", textFieldMaxChar: 12),
        "Spain": PhoneNumberFormat(leadingDigits: "+34", format: "+XX XXX XXX XXX", textFieldMaxChar: 11),
        "Sweden": PhoneNumberFormat(leadingDigits: "+46", format: "+XX (X) XXXX-XXXX", textFieldMaxChar: 11),
        "Switzerland": PhoneNumberFormat(leadingDigits: "+41", format: "+XX (XX) XXX XX XX", textFieldMaxChar: 11),
        "Turkey": PhoneNumberFormat(leadingDigits: "+90", format: "+XX (XXX) XXX-XXXX", textFieldMaxChar: 12),
        "United Arab Emirates": PhoneNumberFormat(leadingDigits: "+971", format: "+XXX (X) XXX-XXXX", textFieldMaxChar: 11),
        "United Kingdom": PhoneNumberFormat(leadingDigits: "+44", format: "+XX (X) XXXX XXX", textFieldMaxChar: 10),
    ]
return formats
    }



struct ContentView: View {
    @State private var PNField: String = ""
    @State private var selectedCountry = "United States"
    
    func maskFormat(with mask: String, phone: String) -> String {
        let numbers = phone.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
        var result = ""
        var index = numbers.startIndex
        var maskIndex = mask.startIndex
        
        while index < numbers.endIndex && maskIndex < mask.endIndex {
            let maskChar = mask[maskIndex]
            let numberChar = numbers[index]
            
            if maskChar == "X" {
                result.append(numberChar)
                index = numbers.index(after: index)
            } else {
                result.append(maskChar)
            }
            
            maskIndex = mask.index(after: maskIndex)
        }
        
        return result
    }
    
    var countries: [String] {
        Array(phoneNumberFormats().keys).sorted()
    }
    
    var body: some View {
        VStack {
            TextField("", text: $PNField, onEditingChanged: { begin in })
                .overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.black, lineWidth: 0.5))
                .onChange(of: PNField) { newValue in
                    PNField = maskFormat(with: phoneNumberFormats()[selectedCountry]!.format, phone: newValue)
                    
                    if PNField.filter({ $0.isNumber }).count <= (phoneNumberFormats()[selectedCountry]!.leadingDigits.filter({ $0.isNumber }).count) {
                        let regex = try? NSRegularExpression(pattern: "[^0-9]+")
                        let matches = regex?.matches(in: newValue, options: [], range: NSRange(location: 0, length: newValue.count))
                        let count = matches?.count ?? 0
                        
                        if count != 0 {
                            PNField = phoneNumberFormats()[selectedCountry]!.leadingDigits
                        } else {
                            PNField = phoneNumberFormats()[selectedCountry]!.leadingDigits + newValue
                        }
                    }
                }
            
            Picker("Country", selection: $selectedCountry) {
                ForEach(countries, id: \.self) { country in
                    Text(country)
                        .onChange(of: selectedCountry) { newValue in
                            PNField = ""
                    }
                }
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Tim
  • 1
  • 1
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 15 '23 at 18:09
-3

Swift 5

String(
    format: "(%@) %@-%@",
    rawNumber.subString(from: 0, to: 2),
    rawNumber.subString(from: 3, to: 5),
    rawNumber.subString(from: 6, to: 9)
)
Amr
  • 2,160
  • 1
  • 15
  • 8