Formattting номер телефона в Swift
Я форматирую свой текстовый текст, как только пользователь начнет вводить номер телефона в этот тип формата 0 (555) 444 66 77 и он работает нормально, но как только я получаю номер с сервера, я получаю его так 05554446677 поэтому, пожалуйста, не могли бы вы сказать мне, как я могу изменить его в том же формате, как только я получаю от сервера?
мой код, как только я начну печатать:
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
    }
}
8 ответов
манипуляции с символами в строке не очень просто. Вам нужно следующее:
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
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'
Swift 4
let s = "05554446677"
let s2 = String(format: "%@ (%@) %@ %@ %@",
    s.substring(to: s.index(s.startIndex, offsetBy: 1)),
    s.substring(with: s.index(s.startIndex, offsetBy: 1) ..< s.index(s.startIndex, offsetBy: 4)),
    s.substring(with: s.index(s.startIndex, offsetBy: 4) ..< s.index(s.startIndex, offsetBy: 7)),
    s.substring(with: s.index(s.startIndex, offsetBy: 7) ..< s.index(s.startIndex, offsetBy: 9)),
    s.substring(with: s.index(s.startIndex, offsetBy: 9) ..< s.index(s.startIndex, offsetBy: 11))
    )
но это не работает, если строка символов подсчитать более или менее 11
ввод номера в маске
private func formattedNumber(number: String) -> String {
    var cleanPhoneNumber = number!.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
    var mask = "+X (XXX) XXX XX-XX"
    var result = ""
    var index = cleanPhoneNumber.startIndex
    for ch in mask.characters {
        if index == cleanPhoneNumber.endIndex {
            break
        }
        if ch == "X" {
            result.append(cleanPhoneNumber[index])
            index = cleanPhoneNumber.index(after: index)
        } else {
            result.append(ch)
        }
    }
    return result
}
Итак, это работа лучше.
"" => ""
"0" => "+0"
"412" => "+4 (12"
"12345678901" => "+1 (234) 567 89-01"
Swift 3, но также должен быть переведен на Swift 4
- 
ErrorHandling enum PhoneNumberFormattingError: Error { case wrongCharactersInPhoneNumber case phoneNumberLongerThanPatternAllowes }
- 
Создать Шаблоны enum PhoneNumberFormattingPatterns: String { case mobile = "+xx (yxx) xxxxxxxxxxx" case home = "+xx (yxxx) xxxx-xxx" }
- 
Вставить Функцию /** 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 } }
- 
использование 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
вы можете использовать эту библиотеку 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, и он будет форматировать текст с шаблоном, который вы установили.
очень простое решение:
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: "#")
здесь есть несколько хороших ответов, но я принял совершенно другой подход и подумал, что поделюсь, если это поможет.
для начала я разбил шаги форматирования и компоненты на свои собственные отдельные обязанности.
формат номера телефона обычно можно разбить на местные, внутренние или международные типы форматов, которые зависят от длины строки.
я определил типы:
/// 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
вот как это работает при вводе:
Я также настроил его, чтобы вы могли просто импортировать его с cocoapods.
pod 'SwiftPhoneFormat', '1.0.0'
затем использовать его:
import SwiftPhoneFormat
var formatter = PhoneFormatter(rulesets: PNFormatRuleset.usParethesis())
let formatted = formatter.format(number: numberString)
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)]))
       }
 }
 
            