格式化Swift中的电话号码

时间:2015-09-02 22:17:12

标签: ios string swift uitextfield string-formatting

一旦用户开始在此格式类型0 (555) 444 66 77中键入电话号码,我就会格式化我的文本文本文件并且它工作正常但是一旦我从服务器获得号码,我就会得到它{{1}那么请你告诉我如何从服务器上获取后以相同的格式编辑它?

我的代码一旦开始输入:

05554446677

13 个答案:

答案 0 :(得分:36)

Swift 3& 4

此解决方案在应用格式设置之前会删除所有非数字字符。如果无法根据假设格式化源电话号码,则返回nil

Swift 4

Swift 4解决方案考虑了CharacterView的弃用,Sting成为CharacterView的字符集合。

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 || (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 || (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])
    }
}

实施例

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"))

示例输出

'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'

答案 1 :(得分:35)

Swift 5

let s = "05554446677"
let s2 = String(format: "%@ (%@) %@ %@ %@",
    String(s[..<s.index(s.startIndex, offsetBy: 1)]),
    String(s[s.index(s.startIndex, offsetBy: 1) ..< s.index(s.startIndex, offsetBy:  4)]), 
    String(s[s.index(s.startIndex, offsetBy: 4) ..< s.index(s.startIndex, offsetBy:  7)]),
    String(s[s.index(s.startIndex, offsetBy: 7) ..< s.index(s.startIndex, offsetBy:  9)]),
    String(s[s.index(s.startIndex, offsetBy: 9) ..< s.index(s.startIndex, offsetBy:  11)]))

但是如果字符串字符数大于或小于11

,它就不起作用

屏蔽号码输入

private func formattedNumber(number: String) -> String {
    let cleanPhoneNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
    let mask = "+X (XXX) XXX-XXXX"

    var result = ""
    var index = cleanPhoneNumber.startIndex
    for ch in mask where index < cleanPhoneNumber.endIndex {
        if ch == "X" {
            result.append(cleanPhoneNumber[index])
            index = cleanPhoneNumber.index(after: index)
        } else {
            result.append(ch)
        }
    }
    return result
}

从UITextField委托方法调用上述函数:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
    textField.text = formattedNumber(number: newString)
    return false
}

所以,这样做会更好。

"" => ""
"0" => "+0"
"412" => "+4 (12"
"12345678901" => "+1 (234) 567-8901"

答案 2 :(得分:12)

真正简单的解决方案:

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

用法:

guard let text = textField.text else { return }
textField.text = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")

答案 3 :(得分:11)

使用String中的字符进行操作并不是非常简单。您需要关注:

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))
)

代码将打印出来 0 (555) 444 66 77

答案 4 :(得分:4)

您可以使用此库https://github.com/luximetr/AnyFormatKit

示例

let textInputController = TextInputController()

let textInput = TextInputField() // or TextInputView or any TextInput
textInputController.textInput = textInput // setting textInput

let formatter = TextInputFormatter(textPattern: "### (###) ###-##-##", prefix: "+12")
textInputController.formatter = formatter // setting formatter

只需将textField设置为此textInputController,它将使用您设置的模式设置文本格式。

答案 5 :(得分:3)

Swift 3但也应该可以翻译为Swift 4

  1. ErrorHandling中

    enum PhoneNumberFormattingError: Error {
        case wrongCharactersInPhoneNumber
        case phoneNumberLongerThanPatternAllowes
    }
    
  2. 创建模式

    enum PhoneNumberFormattingPatterns: String {
        case mobile = "+xx (yxx) xxxxxxxxxxx"
        case home = "+xx (yxxx) xxxx-xxx"
    }
    
  3. 插入函数

    /**
         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. 用法

    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
    

答案 6 :(得分:3)

Swift 5.1更新ДаріяПрокопович绝佳解决方案

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
    }
}

用法:

let formattedText = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")

答案 7 :(得分:2)

快捷键4

创建此函数并调用文本字段事件编辑已更改

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
}

答案 8 :(得分:1)

这里有很多很好的答案,但是我采取了完全不同的方法,并认为如果有帮助,我会分享。

首先,我将格式化步骤和组件分解为各自的职责。

电话号码格式通常可以分为本地,国内或国际格式类型,这些类型随字符串长度而变化。

我定义了类型:

/// 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
}

然后定义可用于格式化电话号码字符串的分隔符:

// 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 "*"
        }
    }
}

接下来,我定义了格式化规则,这些规则指定了在(如电话号码字符串中)插入+,-等分隔符的索引。

// 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
    }
}

定义了这些规则后,我创建了将规则与给定格式类型相关联的规则集。

/// 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 }
}

通过这种方式定义的所有内容,您可以快速设置规则集以适应所需的任何格式。

下面是一个规则集的示例,该规则集为美国通常使用的连字符格式电话号码字符串定义了3条规则:

    // 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)
        ]
    }

(不是这样)繁重的格式化逻辑发生在这里:

// 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
} 

我已经创建了一个带有示例项目的框架,您可以在此处浏览:https://github.com/appteur/phoneformat

这是您输入时的工作方式:

Demo Gif

我还对其进行了设置,因此您只需将其与可可足类一起导入即可。

pod 'SwiftPhoneFormat', '1.0.0'

然后使用它:

import SwiftPhoneFormat

var formatter = PhoneFormatter(rulesets: PNFormatRuleset.usParethesis())
let formatted = formatter.format(number: numberString)

答案 9 :(得分:0)

var formattedPhone = phone
if phone.count == 11 {
    let firstChar = phone[..<phone.index(phone.startIndex, offsetBy: 1)]
    if firstChar == "1" {
        formattedPhone = String(format: "(%@) %@-%@",
                                            String(phone[phone.index(phone.startIndex, offsetBy: 1)..<phone.index(phone.startIndex, offsetBy: 4)]),
                                            String(phone[phone.index(phone.startIndex, offsetBy: 4) ..< phone.index(phone.startIndex, offsetBy: 7)]),
                                            String(phone[phone.index(phone.startIndex, offsetBy: 7) ..< phone.index(phone.startIndex, offsetBy: 11)]))
       }
 }

答案 10 :(得分:0)

此扩展名将完全满足您的要求:

 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"

答案 11 :(得分:0)

如果您不想使用库就可以这样做。 这是最佳示例的链接,或者您可以使用下面的代码。

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

在Swift 5.0中格式化10位电话号码的简单代码段,而不是包含一个大库,只需实现一个委托函数和一个格式化函数即可:

  • UITextFieldDelegate函数
    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
}
  • 格式化功能:
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
}

答案 12 :(得分:-2)

斯威夫特 5

String(
    format: "(%@) %@-%@",
    rawNumber.subString(from: 0, to: 2),
    rawNumber.subString(from: 3, to: 5),
    rawNumber.subString(from: 6, to: 9)
)