ユーザーが電話番号をこの形式に入力し始めたら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
}
}
文字列内の文字を使用した操作はそれほど単純ではありません。以下が必要です:
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.
_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
を出力します
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"
このソリューションは、フォーマットを適用する前に、数字以外の文字を削除します。ソースの電話番号を仮定に従ってフォーマットできない場合、nil
を返します。
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])
}
}
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'
本当に簡単なソリューション:
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: "#")
このライブラリを使用できます 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に設定するだけで、設定したパターンでテキストをフォーマットします。
Swift 3ですが、Swift 4
エラー処理
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
Swift 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
}
ここには多くの良い答えがありますが、私はまったく異なるアプローチを取り、それが役立つ場合に共有すると思いました。
最初に、フォーマットの手順とコンポーネントをそれぞれの責任に分割しました。
通常、電話番号の形式は、文字列の長さによって異なるローカル、国内、または国際的な形式に分類できます。
タイプを定義しました:
/// 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
入力時の動作は次のとおりです。
また、ココアポッドでインポートできるように設定しました。
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)]))
}
}
これは、要件を完全に満たす拡張機能です。
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"