Как я могу отформатировать телефонный номер США в Swift? [Дубликат]

Что такое NullPointerException?

Хорошим местом для начала является JavaDocs . Они охватывают это:

Брошено, когда приложение пытается использовать null в случае, когда требуется объект. К ним относятся:

  • Вызов метода экземпляра нулевого объекта.
  • Доступ или изменение поля нулевого объекта.
  • Выполнение длины null, как если бы это был массив.
  • Доступ или изменение слотов с нулевым значением, как если бы это был массив.
  • Бросать нуль, как если бы это было значение Throwable.

Приложения должны бросать экземпляры этого класса для указания других незаконных видов использования нулевого объекта.

Также, если вы попытаетесь использовать нулевую ссылку с synchronized, который также выдаст это исключение, за JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
  • В противном случае, если значение выражения равно null, NullPointerException.

Как это исправить?

Итак, у вас есть NullPointerException. Как вы это исправите? Возьмем простой пример, который выдает NullPointerException:

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Идентифицирует нулевые значения

. Первый шаг - точно определить , значения которого вызывают исключение . Для этого нам нужно выполнить некоторую отладку. Важно научиться читать stacktrace . Это покажет вам, где было выбрано исключение:

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Здесь мы видим, что исключение выбрано в строке 13 (в методе printString). Посмотрите на строку и проверьте, какие значения равны нулю, добавив протоколирующие операторы или используя отладчик . Мы обнаруживаем, что s имеет значение null, а вызов метода length на него вызывает исключение. Мы видим, что программа перестает бросать исключение, когда s.length() удаляется из метода.

Трассировка, где эти значения взяты из

Затем проверьте, откуда это значение. Следуя вызовам метода, мы видим, что s передается с printString(name) в методе print(), а this.name - null.

Трассировка, где эти значения должны быть установлены

Где установлен this.name? В методе setName(String). С некоторой дополнительной отладкой мы видим, что этот метод вообще не вызывается. Если этот метод был вызван, обязательно проверьте порядок , что эти методы вызывают, а метод set не будет называться после методом печати. ​​

Этого достаточно, чтобы дать нам решение: добавить вызов printer.setName() перед вызовом printer.print().

Другие исправления

Переменная может иметь значение по умолчанию setName может помешать ему установить значение null):

private String name = "";

Либо метод print, либо printString может проверить значение null например:

printString((name == null) ? "" : name);

Или вы можете создать класс, чтобы name всегда имел ненулевое значение :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

См. также:

Я все еще не могу найти проблему

Если вы попытались отладить проблему и до сих пор не имеете решения, вы можете отправить вопрос для получения дополнительной справки, но не забудьте включить то, что вы пробовали до сих пор. Как минимум, включите stacktrace в вопрос и отметьте важные номера строк в коде. Также попробуйте сначала упростить код (см. SSCCE ).

8
задан Avt 2 September 2015 в 23:15
поделиться

9 ответов

Манипуляции с символами в 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

10
ответ дан Avt 18 August 2018 в 00:46
поделиться

Здесь есть несколько хороших ответов, но я принял совершенно другой подход и думал, что буду делиться, если это поможет.

Чтобы начать, я разбил шаги и компоненты форматирования в свои собственные обязанности.

Формат телефонного номера обычно можно разбить на локальный, внутренний или международный формат, который зависит от длины строки.

Я определил типы:

/// 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)
0
ответ дан digitalHound 18 August 2018 в 00:46
поделиться

Вы можете использовать эту библиотеку 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

Просто установите для вашего текстового поля этот textInputController и будет форматировать текст с шаблоном, который вы установили.

1
ответ дан iOS Developer 18 August 2018 в 00:46
поделиться
  • 1
    Я не знаю, почему, но это не работает для меня. Я установил как textInputController.textInput мой UITextField. Я также делаю свой класс TexInputField в инспекторе Identity в раскадровке и делаю подходящее литье. Подано не в живом формате – Michał Ziobro 5 March 2018 в 12:47

Swift 3 & amp; 4

Это решение удаляет любые нечисловые символы перед применением форматирования. Он возвращает nil, если исходный номер телефона не может быть отформатирован в соответствии с предположениями.

Swift 4

Решение Swift 4 учитывает устаревание CharacterView и Sting, становящееся совокупностью символов

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'
22
ответ дан Mobile Dan 18 August 2018 в 00:46
поделиться
  • 1
    Это должно быть правильным решением. Обеспечивает большую гибкость и может быть помещен в собственный класс и использоваться во всем приложении. – TuplingD 7 March 2018 в 14:00

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"
21
ответ дан Roman Filippov 18 August 2018 в 00:46
поделиться

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
    
3
ответ дан Thomas Staltner 18 August 2018 в 00:46
поделиться

Действительно простое решение:

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: "#")
0
ответ дан Дарія Прокопович 18 August 2018 в 00:46
поделиться
0
ответ дан Amr 6 September 2018 в 16:24
поделиться
0
ответ дан Amr 29 October 2018 в 22:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: