Formatting Phone number in Swift

前端 未结 12 1861
天命终不由人
天命终不由人 2020-12-02 09:36

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

相关标签:
12条回答
  • 2020-12-02 09:41

    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
    
    0 讨论(0)
  • 2020-12-02 09:43

    Masked number typing

    /// mask example: `+X (XXX) XXX-XXXX`
    func format(with mask: String, phone: String) -> String {
        let numbers = phone.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
        var result = ""
        var index = numbers.startIndex // numbers iterator
    
        // iterate over the mask characters until the iterator of numbers ends
        for ch in mask where index < numbers.endIndex {
            if ch == "X" {
                // mask requires a number in this place, so take the next one
                result.append(numbers[index])
    
                // move numbers iterator to the next index
                index = numbers.index(after: index)
    
            } else {
                result.append(ch) // just append a mask character
            }
        }
        return result
    }
    

    Call the above function from the UITextField delegate method:

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let text = textField.text else { return false }
        let newString = (text as NSString).replacingCharacters(in: range, with: string)
        textField.text = format(with: "+X (XXX) XXX-XXXX", phone: newString)
        return false
    }
    

    So, that is work better.

    "" => ""
    "0" => "+0"
    "412" => "+4 (12"
    "12345678901" => "+1 (234) 567-8901"
    "a1_b2-c3=d4 e5&f6|g7h8" => "+1 (234) 567-8"
    
    0 讨论(0)
  • 2020-12-02 09:44

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

    Usage:

    guard let text = textField.text else { return }
    textField.text = text.applyPatternOnNumbers(pattern: "+# (###) ###-####", replacmentCharacter: "#")
    
    0 讨论(0)
  • 2020-12-02 09:45

    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
    }
    
    0 讨论(0)
  • 2020-12-02 09:48
    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)]))
           }
     }
    
    0 讨论(0)
  • 2020-12-02 09:52

    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
      
    0 讨论(0)
提交回复
热议问题