├── .gitignore
├── .swift-version
├── .travis.yml
├── Demo
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
└── ViewController.swift
├── LICENSE
├── PhoneNumberFormatter.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ └── PhoneNumberFormatter.xcscheme
├── PhoneNumberFormatter
├── Info.plist
├── PhoneNumberFormatter.h
└── Sources
│ ├── ConfigRepo
│ ├── ConfigurationRepo.swift
│ └── PhoneFormat.swift
│ ├── FormattedTextFieldDelegate.swift
│ ├── PhoneFormattedTextField.swift
│ └── PhoneFormatter.swift
├── PhoneNumberFormatterTests
├── FormatterTests.swift
├── Info.plist
└── TextFieldTests.swift
├── README.md
└── SwiftPhoneNumberFormatter.podspec
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 | *.xcodeproj/project.xcworkspace/xcshareddata
69 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.2
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | osx_image: xcode9
2 | language: swift
3 | xcode_project: PhoneNumberFormatter.xcodeproj
4 | xcode_scheme: PhoneNumberFormatter
5 | xcode_sdk: iphonesimulator11.0
6 | branches:
7 | only:
8 | master
9 |
10 | script:
11 | - set -o pipefail
12 | - xcodebuild clean build test -project PhoneNumberFormatter.xcodeproj -scheme PhoneNumberFormatter -destination 'platform=iOS Simulator,name=iPhone 6,OS=11.0'
13 |
--------------------------------------------------------------------------------
/Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Demo
4 | //
5 | // Created by Sergey Shatunov on 8/30/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication,
17 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | }
23 |
24 | func applicationDidEnterBackground(_ application: UIApplication) {
25 | }
26 |
27 | func applicationWillEnterForeground(_ application: UIApplication) {
28 | }
29 |
30 | func applicationDidBecomeActive(_ application: UIApplication) {
31 | }
32 |
33 | func applicationWillTerminate(_ application: UIApplication) {
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Demo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Demo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Demo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Demo
4 | //
5 | // Created by Sergey Shatunov on 8/30/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import PhoneNumberFormatter
11 |
12 | class ViewController: UIViewController {
13 |
14 | @IBOutlet weak var textField: PhoneFormattedTextField!
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | textField.becomeFirstResponder()
19 |
20 | textField.textDidChangeBlock = { field in
21 | if let text = field?.text, text != "" {
22 | print(text)
23 | } else {
24 | print("No text")
25 | }
26 |
27 | }
28 |
29 | defaultExample()
30 | }
31 |
32 | func defaultExample() {
33 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "# (###) ###-##-##")
34 | }
35 |
36 | func prefixExample() {
37 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "(###) ###-##-##")
38 | textField.prefix = "+7 "
39 | let custom = PhoneFormat(phoneFormat: "(###) ###-##-##", regexp: "^[0-689]\\d*$")
40 | textField.config.add(format: custom)
41 | }
42 |
43 | func doubleFormatExample() {
44 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "##########")
45 | textField.prefix = nil
46 | let custom1 = PhoneFormat(phoneFormat: "+# (###) ###-##-##", regexp: "^7[0-689]\\d*$")
47 | textField.config.add(format: custom1)
48 |
49 | let custom2 = PhoneFormat(phoneFormat: "+### ###-##-##", regexp: "^380\\d*$")
50 | textField.config.add(format: custom2)
51 | }
52 |
53 | func doubleFormatExamplePrefixed() {
54 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "### ### ###")
55 | textField.prefix = "+7 "
56 |
57 | let custom1 = PhoneFormat(phoneFormat: "(###) ###-##-##", regexp: "^1\\d*$")
58 | textField.config.add(format: custom1)
59 |
60 | let custom2 = PhoneFormat(phoneFormat: "(###) ###-###", regexp: "^2\\d*$")
61 | textField.config.add(format: custom2)
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Serheo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 3F4B52321F5CB63700D56C42 /* TextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F4B52311F5CB63700D56C42 /* TextFieldTests.swift */; };
11 | 3F6329041F5670CC007676FA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6329031F5670CC007676FA /* AppDelegate.swift */; };
12 | 3F6329061F5670CC007676FA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6329051F5670CC007676FA /* ViewController.swift */; };
13 | 3F6329091F5670CC007676FA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3F6329071F5670CC007676FA /* Main.storyboard */; };
14 | 3F63290B1F5670CC007676FA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3F63290A1F5670CC007676FA /* Assets.xcassets */; };
15 | 3F63290E1F5670CC007676FA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3F63290C1F5670CC007676FA /* LaunchScreen.storyboard */; };
16 | 3F82D2C21F7747CD004C2D60 /* FormattedTextFieldDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82D2BF1F7747AA004C2D60 /* FormattedTextFieldDelegate.swift */; };
17 | 3F82D2C31F7747CD004C2D60 /* PhoneFormattedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82D2C01F7747AA004C2D60 /* PhoneFormattedTextField.swift */; };
18 | 3F82D2C41F7747CD004C2D60 /* PhoneFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82D2C11F7747AA004C2D60 /* PhoneFormatter.swift */; };
19 | 3F82D2C51F7747D2004C2D60 /* ConfigurationRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82D2BD1F7747AA004C2D60 /* ConfigurationRepo.swift */; };
20 | 3F82D2C61F7747D2004C2D60 /* PhoneFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F82D2BE1F7747AA004C2D60 /* PhoneFormat.swift */; };
21 | 3F980BEA1F567268001E4E55 /* PhoneNumberFormatter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FAC92881F49F3D1001EDF79 /* PhoneNumberFormatter.framework */; };
22 | 3F980BEB1F567268001E4E55 /* PhoneNumberFormatter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3FAC92881F49F3D1001EDF79 /* PhoneNumberFormatter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
23 | 3FAC92921F49F3D1001EDF79 /* PhoneNumberFormatter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FAC92881F49F3D1001EDF79 /* PhoneNumberFormatter.framework */; };
24 | 3FAC92991F49F3D1001EDF79 /* PhoneNumberFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FAC928B1F49F3D1001EDF79 /* PhoneNumberFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
25 | 3FC46A781F5C5D8200ADBE5E /* FormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC46A771F5C5D8200ADBE5E /* FormatterTests.swift */; };
26 | /* End PBXBuildFile section */
27 |
28 | /* Begin PBXContainerItemProxy section */
29 | 3F980BEC1F567268001E4E55 /* PBXContainerItemProxy */ = {
30 | isa = PBXContainerItemProxy;
31 | containerPortal = 3FAC927F1F49F3D0001EDF79 /* Project object */;
32 | proxyType = 1;
33 | remoteGlobalIDString = 3FAC92871F49F3D1001EDF79;
34 | remoteInfo = PhoneNumberFormatter;
35 | };
36 | 3FAC92931F49F3D1001EDF79 /* PBXContainerItemProxy */ = {
37 | isa = PBXContainerItemProxy;
38 | containerPortal = 3FAC927F1F49F3D0001EDF79 /* Project object */;
39 | proxyType = 1;
40 | remoteGlobalIDString = 3FAC92871F49F3D1001EDF79;
41 | remoteInfo = PhoneNumberFormatter;
42 | };
43 | /* End PBXContainerItemProxy section */
44 |
45 | /* Begin PBXCopyFilesBuildPhase section */
46 | 3F980BEE1F567268001E4E55 /* Embed Frameworks */ = {
47 | isa = PBXCopyFilesBuildPhase;
48 | buildActionMask = 2147483647;
49 | dstPath = "";
50 | dstSubfolderSpec = 10;
51 | files = (
52 | 3F980BEB1F567268001E4E55 /* PhoneNumberFormatter.framework in Embed Frameworks */,
53 | );
54 | name = "Embed Frameworks";
55 | runOnlyForDeploymentPostprocessing = 0;
56 | };
57 | /* End PBXCopyFilesBuildPhase section */
58 |
59 | /* Begin PBXFileReference section */
60 | 3F4B52311F5CB63700D56C42 /* TextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTests.swift; sourceTree = ""; };
61 | 3F6329011F5670CC007676FA /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
62 | 3F6329031F5670CC007676FA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
63 | 3F6329051F5670CC007676FA /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
64 | 3F6329081F5670CC007676FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
65 | 3F63290A1F5670CC007676FA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
66 | 3F63290D1F5670CC007676FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
67 | 3F63290F1F5670CC007676FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
68 | 3F82D2BD1F7747AA004C2D60 /* ConfigurationRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationRepo.swift; sourceTree = ""; };
69 | 3F82D2BE1F7747AA004C2D60 /* PhoneFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneFormat.swift; sourceTree = ""; };
70 | 3F82D2BF1F7747AA004C2D60 /* FormattedTextFieldDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedTextFieldDelegate.swift; sourceTree = ""; };
71 | 3F82D2C01F7747AA004C2D60 /* PhoneFormattedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneFormattedTextField.swift; sourceTree = ""; };
72 | 3F82D2C11F7747AA004C2D60 /* PhoneFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneFormatter.swift; sourceTree = ""; };
73 | 3FAC92881F49F3D1001EDF79 /* PhoneNumberFormatter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PhoneNumberFormatter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74 | 3FAC928B1F49F3D1001EDF79 /* PhoneNumberFormatter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PhoneNumberFormatter.h; sourceTree = ""; };
75 | 3FAC928C1F49F3D1001EDF79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
76 | 3FAC92911F49F3D1001EDF79 /* PhoneNumberFormatterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PhoneNumberFormatterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
77 | 3FAC92981F49F3D1001EDF79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
78 | 3FC46A771F5C5D8200ADBE5E /* FormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormatterTests.swift; sourceTree = ""; };
79 | /* End PBXFileReference section */
80 |
81 | /* Begin PBXFrameworksBuildPhase section */
82 | 3F6328FE1F5670CC007676FA /* Frameworks */ = {
83 | isa = PBXFrameworksBuildPhase;
84 | buildActionMask = 2147483647;
85 | files = (
86 | 3F980BEA1F567268001E4E55 /* PhoneNumberFormatter.framework in Frameworks */,
87 | );
88 | runOnlyForDeploymentPostprocessing = 0;
89 | };
90 | 3FAC92841F49F3D1001EDF79 /* Frameworks */ = {
91 | isa = PBXFrameworksBuildPhase;
92 | buildActionMask = 2147483647;
93 | files = (
94 | );
95 | runOnlyForDeploymentPostprocessing = 0;
96 | };
97 | 3FAC928E1F49F3D1001EDF79 /* Frameworks */ = {
98 | isa = PBXFrameworksBuildPhase;
99 | buildActionMask = 2147483647;
100 | files = (
101 | 3FAC92921F49F3D1001EDF79 /* PhoneNumberFormatter.framework in Frameworks */,
102 | );
103 | runOnlyForDeploymentPostprocessing = 0;
104 | };
105 | /* End PBXFrameworksBuildPhase section */
106 |
107 | /* Begin PBXGroup section */
108 | 3F6329021F5670CC007676FA /* Demo */ = {
109 | isa = PBXGroup;
110 | children = (
111 | 3F6329031F5670CC007676FA /* AppDelegate.swift */,
112 | 3F6329051F5670CC007676FA /* ViewController.swift */,
113 | 3F6329071F5670CC007676FA /* Main.storyboard */,
114 | 3F63290A1F5670CC007676FA /* Assets.xcassets */,
115 | 3F63290C1F5670CC007676FA /* LaunchScreen.storyboard */,
116 | 3F63290F1F5670CC007676FA /* Info.plist */,
117 | );
118 | path = Demo;
119 | sourceTree = "";
120 | };
121 | 3F82D2BB1F7747AA004C2D60 /* Sources */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 3F82D2C01F7747AA004C2D60 /* PhoneFormattedTextField.swift */,
125 | 3F82D2BC1F7747AA004C2D60 /* ConfigRepo */,
126 | 3F82D2BF1F7747AA004C2D60 /* FormattedTextFieldDelegate.swift */,
127 | 3F82D2C11F7747AA004C2D60 /* PhoneFormatter.swift */,
128 | );
129 | path = Sources;
130 | sourceTree = "";
131 | };
132 | 3F82D2BC1F7747AA004C2D60 /* ConfigRepo */ = {
133 | isa = PBXGroup;
134 | children = (
135 | 3F82D2BD1F7747AA004C2D60 /* ConfigurationRepo.swift */,
136 | 3F82D2BE1F7747AA004C2D60 /* PhoneFormat.swift */,
137 | );
138 | path = ConfigRepo;
139 | sourceTree = "";
140 | };
141 | 3FAC927E1F49F3D0001EDF79 = {
142 | isa = PBXGroup;
143 | children = (
144 | 3FAC928A1F49F3D1001EDF79 /* PhoneNumberFormatter */,
145 | 3FAC92951F49F3D1001EDF79 /* PhoneNumberFormatterTests */,
146 | 3F6329021F5670CC007676FA /* Demo */,
147 | 3FAC92891F49F3D1001EDF79 /* Products */,
148 | );
149 | sourceTree = "";
150 | };
151 | 3FAC92891F49F3D1001EDF79 /* Products */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 3FAC92881F49F3D1001EDF79 /* PhoneNumberFormatter.framework */,
155 | 3FAC92911F49F3D1001EDF79 /* PhoneNumberFormatterTests.xctest */,
156 | 3F6329011F5670CC007676FA /* Demo.app */,
157 | );
158 | name = Products;
159 | sourceTree = "";
160 | };
161 | 3FAC928A1F49F3D1001EDF79 /* PhoneNumberFormatter */ = {
162 | isa = PBXGroup;
163 | children = (
164 | 3FAC928B1F49F3D1001EDF79 /* PhoneNumberFormatter.h */,
165 | 3F82D2BB1F7747AA004C2D60 /* Sources */,
166 | 3FAC928C1F49F3D1001EDF79 /* Info.plist */,
167 | );
168 | path = PhoneNumberFormatter;
169 | sourceTree = "";
170 | };
171 | 3FAC92951F49F3D1001EDF79 /* PhoneNumberFormatterTests */ = {
172 | isa = PBXGroup;
173 | children = (
174 | 3FC46A771F5C5D8200ADBE5E /* FormatterTests.swift */,
175 | 3F4B52311F5CB63700D56C42 /* TextFieldTests.swift */,
176 | 3FAC92981F49F3D1001EDF79 /* Info.plist */,
177 | );
178 | path = PhoneNumberFormatterTests;
179 | sourceTree = "";
180 | };
181 | /* End PBXGroup section */
182 |
183 | /* Begin PBXHeadersBuildPhase section */
184 | 3FAC92851F49F3D1001EDF79 /* Headers */ = {
185 | isa = PBXHeadersBuildPhase;
186 | buildActionMask = 2147483647;
187 | files = (
188 | 3FAC92991F49F3D1001EDF79 /* PhoneNumberFormatter.h in Headers */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXHeadersBuildPhase section */
193 |
194 | /* Begin PBXNativeTarget section */
195 | 3F6329001F5670CC007676FA /* Demo */ = {
196 | isa = PBXNativeTarget;
197 | buildConfigurationList = 3F6329121F5670CC007676FA /* Build configuration list for PBXNativeTarget "Demo" */;
198 | buildPhases = (
199 | 3F6328FD1F5670CC007676FA /* Sources */,
200 | 3F6328FE1F5670CC007676FA /* Frameworks */,
201 | 3F6328FF1F5670CC007676FA /* Resources */,
202 | 3F980BEE1F567268001E4E55 /* Embed Frameworks */,
203 | );
204 | buildRules = (
205 | );
206 | dependencies = (
207 | 3F980BED1F567268001E4E55 /* PBXTargetDependency */,
208 | );
209 | name = Demo;
210 | productName = Demo;
211 | productReference = 3F6329011F5670CC007676FA /* Demo.app */;
212 | productType = "com.apple.product-type.application";
213 | };
214 | 3FAC92871F49F3D1001EDF79 /* PhoneNumberFormatter */ = {
215 | isa = PBXNativeTarget;
216 | buildConfigurationList = 3FAC929C1F49F3D1001EDF79 /* Build configuration list for PBXNativeTarget "PhoneNumberFormatter" */;
217 | buildPhases = (
218 | 3FAC92831F49F3D1001EDF79 /* Sources */,
219 | 3FAC92841F49F3D1001EDF79 /* Frameworks */,
220 | 3FAC92851F49F3D1001EDF79 /* Headers */,
221 | 3FAC92861F49F3D1001EDF79 /* Resources */,
222 | 3F394AD51F72338600CBDB53 /* SwiftLint */,
223 | );
224 | buildRules = (
225 | );
226 | dependencies = (
227 | );
228 | name = PhoneNumberFormatter;
229 | productName = PhoneNumberFormatter;
230 | productReference = 3FAC92881F49F3D1001EDF79 /* PhoneNumberFormatter.framework */;
231 | productType = "com.apple.product-type.framework";
232 | };
233 | 3FAC92901F49F3D1001EDF79 /* PhoneNumberFormatterTests */ = {
234 | isa = PBXNativeTarget;
235 | buildConfigurationList = 3FAC929F1F49F3D1001EDF79 /* Build configuration list for PBXNativeTarget "PhoneNumberFormatterTests" */;
236 | buildPhases = (
237 | 3FAC928D1F49F3D1001EDF79 /* Sources */,
238 | 3FAC928E1F49F3D1001EDF79 /* Frameworks */,
239 | 3FAC928F1F49F3D1001EDF79 /* Resources */,
240 | );
241 | buildRules = (
242 | );
243 | dependencies = (
244 | 3FAC92941F49F3D1001EDF79 /* PBXTargetDependency */,
245 | );
246 | name = PhoneNumberFormatterTests;
247 | productName = PhoneNumberFormatterTests;
248 | productReference = 3FAC92911F49F3D1001EDF79 /* PhoneNumberFormatterTests.xctest */;
249 | productType = "com.apple.product-type.bundle.unit-test";
250 | };
251 | /* End PBXNativeTarget section */
252 |
253 | /* Begin PBXProject section */
254 | 3FAC927F1F49F3D0001EDF79 /* Project object */ = {
255 | isa = PBXProject;
256 | attributes = {
257 | LastSwiftUpdateCheck = 0900;
258 | LastUpgradeCheck = 1010;
259 | ORGANIZATIONNAME = SHS;
260 | TargetAttributes = {
261 | 3F6329001F5670CC007676FA = {
262 | CreatedOnToolsVersion = 9.0;
263 | ProvisioningStyle = Automatic;
264 | };
265 | 3FAC92871F49F3D1001EDF79 = {
266 | CreatedOnToolsVersion = 9.0;
267 | LastSwiftMigration = 0900;
268 | };
269 | 3FAC92901F49F3D1001EDF79 = {
270 | CreatedOnToolsVersion = 9.0;
271 | };
272 | };
273 | };
274 | buildConfigurationList = 3FAC92821F49F3D0001EDF79 /* Build configuration list for PBXProject "PhoneNumberFormatter" */;
275 | compatibilityVersion = "Xcode 8.0";
276 | developmentRegion = en;
277 | hasScannedForEncodings = 0;
278 | knownRegions = (
279 | en,
280 | Base,
281 | );
282 | mainGroup = 3FAC927E1F49F3D0001EDF79;
283 | productRefGroup = 3FAC92891F49F3D1001EDF79 /* Products */;
284 | projectDirPath = "";
285 | projectRoot = "";
286 | targets = (
287 | 3FAC92871F49F3D1001EDF79 /* PhoneNumberFormatter */,
288 | 3F6329001F5670CC007676FA /* Demo */,
289 | 3FAC92901F49F3D1001EDF79 /* PhoneNumberFormatterTests */,
290 | );
291 | };
292 | /* End PBXProject section */
293 |
294 | /* Begin PBXResourcesBuildPhase section */
295 | 3F6328FF1F5670CC007676FA /* Resources */ = {
296 | isa = PBXResourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | 3F63290E1F5670CC007676FA /* LaunchScreen.storyboard in Resources */,
300 | 3F63290B1F5670CC007676FA /* Assets.xcassets in Resources */,
301 | 3F6329091F5670CC007676FA /* Main.storyboard in Resources */,
302 | );
303 | runOnlyForDeploymentPostprocessing = 0;
304 | };
305 | 3FAC92861F49F3D1001EDF79 /* Resources */ = {
306 | isa = PBXResourcesBuildPhase;
307 | buildActionMask = 2147483647;
308 | files = (
309 | );
310 | runOnlyForDeploymentPostprocessing = 0;
311 | };
312 | 3FAC928F1F49F3D1001EDF79 /* Resources */ = {
313 | isa = PBXResourcesBuildPhase;
314 | buildActionMask = 2147483647;
315 | files = (
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | };
319 | /* End PBXResourcesBuildPhase section */
320 |
321 | /* Begin PBXShellScriptBuildPhase section */
322 | 3F394AD51F72338600CBDB53 /* SwiftLint */ = {
323 | isa = PBXShellScriptBuildPhase;
324 | buildActionMask = 2147483647;
325 | files = (
326 | );
327 | inputPaths = (
328 | );
329 | name = SwiftLint;
330 | outputPaths = (
331 | );
332 | runOnlyForDeploymentPostprocessing = 0;
333 | shellPath = /bin/sh;
334 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
335 | };
336 | /* End PBXShellScriptBuildPhase section */
337 |
338 | /* Begin PBXSourcesBuildPhase section */
339 | 3F6328FD1F5670CC007676FA /* Sources */ = {
340 | isa = PBXSourcesBuildPhase;
341 | buildActionMask = 2147483647;
342 | files = (
343 | 3F6329061F5670CC007676FA /* ViewController.swift in Sources */,
344 | 3F6329041F5670CC007676FA /* AppDelegate.swift in Sources */,
345 | );
346 | runOnlyForDeploymentPostprocessing = 0;
347 | };
348 | 3FAC92831F49F3D1001EDF79 /* Sources */ = {
349 | isa = PBXSourcesBuildPhase;
350 | buildActionMask = 2147483647;
351 | files = (
352 | 3F82D2C61F7747D2004C2D60 /* PhoneFormat.swift in Sources */,
353 | 3F82D2C51F7747D2004C2D60 /* ConfigurationRepo.swift in Sources */,
354 | 3F82D2C41F7747CD004C2D60 /* PhoneFormatter.swift in Sources */,
355 | 3F82D2C21F7747CD004C2D60 /* FormattedTextFieldDelegate.swift in Sources */,
356 | 3F82D2C31F7747CD004C2D60 /* PhoneFormattedTextField.swift in Sources */,
357 | );
358 | runOnlyForDeploymentPostprocessing = 0;
359 | };
360 | 3FAC928D1F49F3D1001EDF79 /* Sources */ = {
361 | isa = PBXSourcesBuildPhase;
362 | buildActionMask = 2147483647;
363 | files = (
364 | 3FC46A781F5C5D8200ADBE5E /* FormatterTests.swift in Sources */,
365 | 3F4B52321F5CB63700D56C42 /* TextFieldTests.swift in Sources */,
366 | );
367 | runOnlyForDeploymentPostprocessing = 0;
368 | };
369 | /* End PBXSourcesBuildPhase section */
370 |
371 | /* Begin PBXTargetDependency section */
372 | 3F980BED1F567268001E4E55 /* PBXTargetDependency */ = {
373 | isa = PBXTargetDependency;
374 | target = 3FAC92871F49F3D1001EDF79 /* PhoneNumberFormatter */;
375 | targetProxy = 3F980BEC1F567268001E4E55 /* PBXContainerItemProxy */;
376 | };
377 | 3FAC92941F49F3D1001EDF79 /* PBXTargetDependency */ = {
378 | isa = PBXTargetDependency;
379 | target = 3FAC92871F49F3D1001EDF79 /* PhoneNumberFormatter */;
380 | targetProxy = 3FAC92931F49F3D1001EDF79 /* PBXContainerItemProxy */;
381 | };
382 | /* End PBXTargetDependency section */
383 |
384 | /* Begin PBXVariantGroup section */
385 | 3F6329071F5670CC007676FA /* Main.storyboard */ = {
386 | isa = PBXVariantGroup;
387 | children = (
388 | 3F6329081F5670CC007676FA /* Base */,
389 | );
390 | name = Main.storyboard;
391 | sourceTree = "";
392 | };
393 | 3F63290C1F5670CC007676FA /* LaunchScreen.storyboard */ = {
394 | isa = PBXVariantGroup;
395 | children = (
396 | 3F63290D1F5670CC007676FA /* Base */,
397 | );
398 | name = LaunchScreen.storyboard;
399 | sourceTree = "";
400 | };
401 | /* End PBXVariantGroup section */
402 |
403 | /* Begin XCBuildConfiguration section */
404 | 3F6329101F5670CC007676FA /* Debug */ = {
405 | isa = XCBuildConfiguration;
406 | buildSettings = {
407 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
408 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
409 | CODE_SIGN_STYLE = Automatic;
410 | DEVELOPMENT_TEAM = E6P53MM964;
411 | INFOPLIST_FILE = Demo/Info.plist;
412 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
414 | PRODUCT_BUNDLE_IDENTIFIER = com.shs.Demo;
415 | PRODUCT_NAME = "$(TARGET_NAME)";
416 | SWIFT_VERSION = 4.2;
417 | TARGETED_DEVICE_FAMILY = 1;
418 | };
419 | name = Debug;
420 | };
421 | 3F6329111F5670CC007676FA /* Release */ = {
422 | isa = XCBuildConfiguration;
423 | buildSettings = {
424 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
426 | CODE_SIGN_STYLE = Automatic;
427 | DEVELOPMENT_TEAM = E6P53MM964;
428 | INFOPLIST_FILE = Demo/Info.plist;
429 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
431 | PRODUCT_BUNDLE_IDENTIFIER = com.shs.Demo;
432 | PRODUCT_NAME = "$(TARGET_NAME)";
433 | SWIFT_VERSION = 4.2;
434 | TARGETED_DEVICE_FAMILY = 1;
435 | };
436 | name = Release;
437 | };
438 | 3FAC929A1F49F3D1001EDF79 /* Debug */ = {
439 | isa = XCBuildConfiguration;
440 | buildSettings = {
441 | ALWAYS_SEARCH_USER_PATHS = NO;
442 | CLANG_ANALYZER_NONNULL = YES;
443 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
444 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
445 | CLANG_CXX_LIBRARY = "libc++";
446 | CLANG_ENABLE_MODULES = YES;
447 | CLANG_ENABLE_OBJC_ARC = YES;
448 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
449 | CLANG_WARN_BOOL_CONVERSION = YES;
450 | CLANG_WARN_COMMA = YES;
451 | CLANG_WARN_CONSTANT_CONVERSION = YES;
452 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
453 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
454 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
455 | CLANG_WARN_EMPTY_BODY = YES;
456 | CLANG_WARN_ENUM_CONVERSION = YES;
457 | CLANG_WARN_INFINITE_RECURSION = YES;
458 | CLANG_WARN_INT_CONVERSION = YES;
459 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
460 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
461 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
462 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
463 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
464 | CLANG_WARN_STRICT_PROTOTYPES = YES;
465 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
466 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
467 | CLANG_WARN_UNREACHABLE_CODE = YES;
468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
469 | CODE_SIGN_IDENTITY = "iPhone Developer";
470 | COPY_PHASE_STRIP = NO;
471 | CURRENT_PROJECT_VERSION = 1;
472 | DEBUG_INFORMATION_FORMAT = dwarf;
473 | ENABLE_STRICT_OBJC_MSGSEND = YES;
474 | ENABLE_TESTABILITY = YES;
475 | GCC_C_LANGUAGE_STANDARD = gnu11;
476 | GCC_DYNAMIC_NO_PIC = NO;
477 | GCC_NO_COMMON_BLOCKS = YES;
478 | GCC_OPTIMIZATION_LEVEL = 0;
479 | GCC_PREPROCESSOR_DEFINITIONS = (
480 | "DEBUG=1",
481 | "$(inherited)",
482 | );
483 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
484 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
485 | GCC_WARN_UNDECLARED_SELECTOR = YES;
486 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
487 | GCC_WARN_UNUSED_FUNCTION = YES;
488 | GCC_WARN_UNUSED_VARIABLE = YES;
489 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
490 | MTL_ENABLE_DEBUG_INFO = YES;
491 | ONLY_ACTIVE_ARCH = YES;
492 | SDKROOT = iphoneos;
493 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
494 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
495 | VERSIONING_SYSTEM = "apple-generic";
496 | VERSION_INFO_PREFIX = "";
497 | };
498 | name = Debug;
499 | };
500 | 3FAC929B1F49F3D1001EDF79 /* Release */ = {
501 | isa = XCBuildConfiguration;
502 | buildSettings = {
503 | ALWAYS_SEARCH_USER_PATHS = NO;
504 | CLANG_ANALYZER_NONNULL = YES;
505 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
506 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
507 | CLANG_CXX_LIBRARY = "libc++";
508 | CLANG_ENABLE_MODULES = YES;
509 | CLANG_ENABLE_OBJC_ARC = YES;
510 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
511 | CLANG_WARN_BOOL_CONVERSION = YES;
512 | CLANG_WARN_COMMA = YES;
513 | CLANG_WARN_CONSTANT_CONVERSION = YES;
514 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
515 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
516 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
517 | CLANG_WARN_EMPTY_BODY = YES;
518 | CLANG_WARN_ENUM_CONVERSION = YES;
519 | CLANG_WARN_INFINITE_RECURSION = YES;
520 | CLANG_WARN_INT_CONVERSION = YES;
521 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
522 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
523 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
524 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
525 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
526 | CLANG_WARN_STRICT_PROTOTYPES = YES;
527 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
528 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
529 | CLANG_WARN_UNREACHABLE_CODE = YES;
530 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
531 | CODE_SIGN_IDENTITY = "iPhone Developer";
532 | COPY_PHASE_STRIP = NO;
533 | CURRENT_PROJECT_VERSION = 1;
534 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
535 | ENABLE_NS_ASSERTIONS = NO;
536 | ENABLE_STRICT_OBJC_MSGSEND = YES;
537 | GCC_C_LANGUAGE_STANDARD = gnu11;
538 | GCC_NO_COMMON_BLOCKS = YES;
539 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
540 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
541 | GCC_WARN_UNDECLARED_SELECTOR = YES;
542 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
543 | GCC_WARN_UNUSED_FUNCTION = YES;
544 | GCC_WARN_UNUSED_VARIABLE = YES;
545 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
546 | MTL_ENABLE_DEBUG_INFO = NO;
547 | SDKROOT = iphoneos;
548 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
549 | VALIDATE_PRODUCT = YES;
550 | VERSIONING_SYSTEM = "apple-generic";
551 | VERSION_INFO_PREFIX = "";
552 | };
553 | name = Release;
554 | };
555 | 3FAC929D1F49F3D1001EDF79 /* Debug */ = {
556 | isa = XCBuildConfiguration;
557 | buildSettings = {
558 | CLANG_ENABLE_MODULES = YES;
559 | CODE_SIGN_IDENTITY = "";
560 | DEFINES_MODULE = YES;
561 | DYLIB_COMPATIBILITY_VERSION = 1;
562 | DYLIB_CURRENT_VERSION = 1;
563 | DYLIB_INSTALL_NAME_BASE = "@rpath";
564 | INFOPLIST_FILE = PhoneNumberFormatter/Info.plist;
565 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
566 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
567 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
568 | PRODUCT_BUNDLE_IDENTIFIER = com.shs.PhoneNumberFormatter;
569 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
570 | SKIP_INSTALL = YES;
571 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
572 | SWIFT_VERSION = 4.2;
573 | TARGETED_DEVICE_FAMILY = "1,2";
574 | };
575 | name = Debug;
576 | };
577 | 3FAC929E1F49F3D1001EDF79 /* Release */ = {
578 | isa = XCBuildConfiguration;
579 | buildSettings = {
580 | CLANG_ENABLE_MODULES = YES;
581 | CODE_SIGN_IDENTITY = "";
582 | DEFINES_MODULE = YES;
583 | DYLIB_COMPATIBILITY_VERSION = 1;
584 | DYLIB_CURRENT_VERSION = 1;
585 | DYLIB_INSTALL_NAME_BASE = "@rpath";
586 | INFOPLIST_FILE = PhoneNumberFormatter/Info.plist;
587 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
588 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
589 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
590 | PRODUCT_BUNDLE_IDENTIFIER = com.shs.PhoneNumberFormatter;
591 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
592 | SKIP_INSTALL = YES;
593 | SWIFT_VERSION = 4.2;
594 | TARGETED_DEVICE_FAMILY = "1,2";
595 | };
596 | name = Release;
597 | };
598 | 3FAC92A01F49F3D1001EDF79 /* Debug */ = {
599 | isa = XCBuildConfiguration;
600 | buildSettings = {
601 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
602 | INFOPLIST_FILE = PhoneNumberFormatterTests/Info.plist;
603 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
604 | PRODUCT_BUNDLE_IDENTIFIER = com.shs.PhoneNumberFormatterTests;
605 | PRODUCT_NAME = "$(TARGET_NAME)";
606 | SWIFT_VERSION = 4.2;
607 | TARGETED_DEVICE_FAMILY = "1,2";
608 | };
609 | name = Debug;
610 | };
611 | 3FAC92A11F49F3D1001EDF79 /* Release */ = {
612 | isa = XCBuildConfiguration;
613 | buildSettings = {
614 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
615 | INFOPLIST_FILE = PhoneNumberFormatterTests/Info.plist;
616 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
617 | PRODUCT_BUNDLE_IDENTIFIER = com.shs.PhoneNumberFormatterTests;
618 | PRODUCT_NAME = "$(TARGET_NAME)";
619 | SWIFT_VERSION = 4.2;
620 | TARGETED_DEVICE_FAMILY = "1,2";
621 | };
622 | name = Release;
623 | };
624 | /* End XCBuildConfiguration section */
625 |
626 | /* Begin XCConfigurationList section */
627 | 3F6329121F5670CC007676FA /* Build configuration list for PBXNativeTarget "Demo" */ = {
628 | isa = XCConfigurationList;
629 | buildConfigurations = (
630 | 3F6329101F5670CC007676FA /* Debug */,
631 | 3F6329111F5670CC007676FA /* Release */,
632 | );
633 | defaultConfigurationIsVisible = 0;
634 | defaultConfigurationName = Release;
635 | };
636 | 3FAC92821F49F3D0001EDF79 /* Build configuration list for PBXProject "PhoneNumberFormatter" */ = {
637 | isa = XCConfigurationList;
638 | buildConfigurations = (
639 | 3FAC929A1F49F3D1001EDF79 /* Debug */,
640 | 3FAC929B1F49F3D1001EDF79 /* Release */,
641 | );
642 | defaultConfigurationIsVisible = 0;
643 | defaultConfigurationName = Release;
644 | };
645 | 3FAC929C1F49F3D1001EDF79 /* Build configuration list for PBXNativeTarget "PhoneNumberFormatter" */ = {
646 | isa = XCConfigurationList;
647 | buildConfigurations = (
648 | 3FAC929D1F49F3D1001EDF79 /* Debug */,
649 | 3FAC929E1F49F3D1001EDF79 /* Release */,
650 | );
651 | defaultConfigurationIsVisible = 0;
652 | defaultConfigurationName = Release;
653 | };
654 | 3FAC929F1F49F3D1001EDF79 /* Build configuration list for PBXNativeTarget "PhoneNumberFormatterTests" */ = {
655 | isa = XCConfigurationList;
656 | buildConfigurations = (
657 | 3FAC92A01F49F3D1001EDF79 /* Debug */,
658 | 3FAC92A11F49F3D1001EDF79 /* Release */,
659 | );
660 | defaultConfigurationIsVisible = 0;
661 | defaultConfigurationName = Release;
662 | };
663 | /* End XCConfigurationList section */
664 | };
665 | rootObject = 3FAC927F1F49F3D0001EDF79 /* Project object */;
666 | }
667 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter.xcodeproj/xcshareddata/xcschemes/PhoneNumberFormatter.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/PhoneNumberFormatter.h:
--------------------------------------------------------------------------------
1 | //
2 | // PhoneNumberFormatter.h
3 | // PhoneNumberFormatter
4 | //
5 | // Created by Sergey Shatunov on 8/20/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for PhoneNumberFormatter.
12 | FOUNDATION_EXPORT double PhoneNumberFormatterVersionNumber;
13 |
14 | //! Project version string for PhoneNumberFormatter.
15 | FOUNDATION_EXPORT const unsigned char PhoneNumberFormatterVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/Sources/ConfigRepo/ConfigurationRepo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurationRepo.swift
3 | // PhoneNumberFormatter
4 | //
5 | // Created by Sergey Shatunov on 8/20/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /**
12 | List of all possible formatters of the TextField.
13 | Contains at least default one.
14 | */
15 | final public class ConfigurationRepo {
16 |
17 | private var customConfigs: [PhoneFormat] = []
18 |
19 | /**
20 | Default configuration
21 | */
22 | public var defaultConfiguration: PhoneFormat = PhoneFormat(defaultPhoneFormat: "#############")
23 |
24 | init() {
25 | }
26 |
27 | init(defaultFormat: PhoneFormat) {
28 | self.defaultConfiguration = defaultFormat
29 | }
30 |
31 | func getDefaultConfig() -> PhoneFormat {
32 | return defaultConfiguration
33 | }
34 |
35 | func getUserConfigs() -> [PhoneFormat] {
36 | return customConfigs
37 | }
38 |
39 | /**
40 | Add new custom format
41 | */
42 | public func add(format: PhoneFormat) {
43 | customConfigs.append(format)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/Sources/ConfigRepo/PhoneFormat.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhoneFormat.swift
3 | // PhoneNumberFormatter
4 | //
5 | // Created by Sergey Shatunov on 8/20/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /**
12 | Phone Format Class. Conatin format and related regexp
13 | */
14 | public struct PhoneFormat {
15 |
16 | /**
17 | Phone format
18 | */
19 | public let phoneFormat: String
20 |
21 | /**
22 | Phone regexp for the format
23 | */
24 | public let regexp: String
25 |
26 | public init(defaultPhoneFormat: String) {
27 | self.phoneFormat = defaultPhoneFormat
28 | self.regexp = "*"
29 | }
30 |
31 | public init(phoneFormat: String, regexp: String) {
32 | self.phoneFormat = phoneFormat
33 | self.regexp = regexp
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/Sources/FormattedTextFieldDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormattedTextFieldDelegate.swift
3 | // PhoneNumberFormatter
4 | //
5 | // Created by Sergey Shatunov on 8/20/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | final class FormattedTextFieldDelegate: NSObject, UITextFieldDelegate {
12 | weak var userDelegate: UITextFieldDelegate?
13 |
14 | var textDidChangeBlock: ((_ textField: UITextField?) -> Void)?
15 | var prefix: String?
16 | var hasPredictiveInput: Bool = true
17 |
18 | private let formatter: PhoneFormatter
19 | init(formatter: PhoneFormatter) {
20 | self.formatter = formatter
21 | }
22 |
23 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
24 | replacementString string: String) -> Bool {
25 | if let prefix = prefix, range.location < prefix.count {
26 | return false
27 | }
28 |
29 | let resultText = textField.text ?? ""
30 | let caretPosition = formatter.pushCaretPosition(text: resultText, range: range)
31 |
32 | let isDeleting = string.count == 0
33 | let newString: String
34 | if isDeleting {
35 | newString = formatter.formattedRemove(text: resultText, range: range)
36 | } else {
37 | let rangeExpressionStart = resultText.index(resultText.startIndex, offsetBy: range.location)
38 | let rangeExpressionEnd = resultText.index(resultText.startIndex, offsetBy: range.location + range.length)
39 | newString = resultText.replacingCharacters(in: rangeExpressionStart.. Bool {
65 | return userDelegate?.textFieldShouldBeginEditing?(textField) ?? true
66 | }
67 |
68 | func textFieldDidBeginEditing(_ textField: UITextField) {
69 | userDelegate?.textFieldDidBeginEditing?(textField)
70 | }
71 |
72 | func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
73 | return userDelegate?.textFieldShouldEndEditing?(textField) ?? true
74 | }
75 |
76 | func textFieldDidEndEditing(_ textField: UITextField) {
77 | userDelegate?.textFieldDidEndEditing?(textField)
78 | }
79 |
80 | func textFieldShouldClear(_ textField: UITextField) -> Bool {
81 | if let userResult = userDelegate?.textFieldShouldClear?(textField) {
82 | return userResult
83 | }
84 |
85 | if let prefix = prefix {
86 | textField.text = prefix
87 | return false
88 | } else {
89 | return true
90 | }
91 | }
92 |
93 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
94 | return userDelegate?.textFieldShouldReturn?(textField) ?? true
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/Sources/PhoneFormattedTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhoneFormattedTextField.swift
3 | // PhoneNumberFormatter
4 | //
5 | // Created by Sergey Shatunov on 8/20/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /**
12 | UITextField subclass to handle phone numbers formats
13 | */
14 | public class PhoneFormattedTextField: UITextField {
15 |
16 | private let formatProxy: FormattedTextFieldDelegate
17 | private let formatter: PhoneFormatter
18 |
19 | /**
20 | Use is to configure format properties
21 | */
22 | public let config: ConfigurationRepo
23 |
24 | /**
25 | If you have a predictive input enabled.
26 | Default is true.
27 | */
28 | public var hasPredictiveInput: Bool {
29 | set {
30 | formatProxy.hasPredictiveInput = newValue
31 | }
32 | get {
33 | return formatProxy.hasPredictiveInput
34 | }
35 | }
36 |
37 | /**
38 | Prefix for all formats
39 | */
40 | public var prefix: String? {
41 | set {
42 | formatProxy.prefix = newValue
43 | self.text = newValue
44 | }
45 | get {
46 | return formatProxy.prefix
47 | }
48 | }
49 |
50 | public override init(frame: CGRect) {
51 | config = ConfigurationRepo()
52 | formatter = PhoneFormatter(config: config)
53 | formatProxy = FormattedTextFieldDelegate(formatter: formatter)
54 | super.init(frame: frame)
55 |
56 | super.delegate = formatProxy
57 | self.keyboardType = .numberPad
58 | }
59 |
60 | public required init?(coder aDecoder: NSCoder) {
61 | config = ConfigurationRepo()
62 | formatter = PhoneFormatter(config: config)
63 | formatProxy = FormattedTextFieldDelegate(formatter: formatter)
64 | super.init(coder: aDecoder)
65 |
66 | super.delegate = formatProxy
67 | self.keyboardType = .numberPad
68 | }
69 |
70 | override public var delegate: UITextFieldDelegate? {
71 | get {
72 | return formatProxy.userDelegate
73 | }
74 | set {
75 | formatProxy.userDelegate = newValue
76 | }
77 | }
78 |
79 | /**
80 | Block is called on text change
81 | */
82 | public var textDidChangeBlock:((_ textField: UITextField?) -> Void)? {
83 | get {
84 | return formatProxy.textDidChangeBlock
85 | }
86 | set {
87 | formatProxy.textDidChangeBlock = newValue
88 | }
89 | }
90 |
91 | /**
92 | Return phone number without format. Ex: 89201235678
93 | */
94 | public func phoneNumber() -> String? {
95 | return formatter.digitOnlyString(text: self.text)
96 | }
97 |
98 | /**
99 | Return phone number without format and prefix
100 | */
101 | public func phoneNumberWithoutPrefix() -> String? {
102 | if var current = self.text, let prefixString = self.prefix, current.hasPrefix(prefixString) {
103 | current.removeFirst(prefixString.count)
104 | return formatter.digitOnlyString(text: current)
105 | } else {
106 | return formatter.digitOnlyString(text: self.text)
107 | }
108 | }
109 |
110 | public var formattedText: String? {
111 | get {
112 | return self.text
113 | }
114 |
115 | set {
116 | if let value = newValue {
117 | let result = formatter.formatText(text: value, prefix: prefix)
118 | self.text = result.text
119 | } else {
120 | self.text = ""
121 | }
122 |
123 | self.textDidChangeBlock?(self)
124 | self.sendActions(for: .valueChanged)
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/PhoneNumberFormatter/Sources/PhoneFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhoneFormatter.swift
3 | // PhoneNumberFormatter
4 | //
5 | // Created by Sergey Shatunov on 8/20/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | struct PhoneFormatterResult {
12 | let text: String
13 |
14 | init(text: String) {
15 | self.text = text
16 | }
17 | }
18 |
19 | final class PhoneFormatter {
20 |
21 | let config: ConfigurationRepo
22 | init(config: ConfigurationRepo) {
23 | self.config = config
24 | }
25 |
26 | private let patternSymbol: Character = "#"
27 | private func isRequireSubstitute(char: Character) -> Bool {
28 | return patternSymbol == char
29 | }
30 |
31 | private let valuableChars: [Character] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
32 | func isValuableChar(char: Character) -> Bool {
33 | return valuableChars.contains(char) ? true : false
34 | }
35 |
36 | func digitOnlyString(text: String?) -> String? {
37 | guard let text = text else {
38 | return nil
39 | }
40 |
41 | if let regex = try? NSRegularExpression(pattern: "\\D",
42 | options: [NSRegularExpression.Options.caseInsensitive]) {
43 | let range = NSRange(location: 0, length: text.count)
44 | return regex.stringByReplacingMatches(in: text, options: [], range: range, withTemplate: "")
45 | }
46 | return nil
47 | }
48 |
49 | private func isMatched(text: String, pattern: String) -> Bool {
50 | guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {
51 | return false
52 | }
53 |
54 | let range = NSRange(location: 0, length: text.count)
55 | let match = regex.firstMatch(in: text, options: [], range: range)
56 | if let matchObject = match, matchObject.range.location != NSNotFound {
57 | return true
58 | } else {
59 | return false
60 | }
61 | }
62 |
63 | private func getAppropriateConfig(text: String, in repo: ConfigurationRepo) -> PhoneFormat {
64 | for item in config.getUserConfigs() {
65 | if isMatched(text: text, pattern: item.regexp) {
66 | return item
67 | }
68 | }
69 | return repo.getDefaultConfig()
70 | }
71 |
72 | func formatText(text: String, prefix: String? = nil) -> PhoneFormatterResult {
73 | let lastPossibleFormat = getAppropriateConfig(text: text, in: config)
74 |
75 | let cleanNumber = removeFormatFrom(text: text, format: lastPossibleFormat, prefix: prefix) ?? ""
76 |
77 | let appropriateConfig = getAppropriateConfig(text: cleanNumber, in: config)
78 | let result = applyFormat(text: cleanNumber, format: appropriateConfig, prefix: prefix)
79 | return PhoneFormatterResult(text: result)
80 | }
81 |
82 | func formattedRemove(text: String, range: NSRange) -> String {
83 | var possibleString = Array(text)
84 | let rangeExpressionStart = text.index(text.startIndex, offsetBy: range.location)
85 | let rangeExpressionEnd = text.index(text.startIndex, offsetBy: range.location + range.length)
86 |
87 | let targetSubstring = text[rangeExpressionStart.. String? {
105 | var unprefixedString = text
106 | if let prefixString = prefix, unprefixedString.hasPrefix(prefixString) {
107 | unprefixedString.removeFirst(prefixString.count)
108 | }
109 |
110 | let phoneFormat = format.phoneFormat
111 | var removeRanges: [NSRange] = []
112 |
113 | let min = [text.count, format.phoneFormat.count].min() ?? 0
114 | for idx in 0 ..< min {
115 | let index = phoneFormat.index(phoneFormat.startIndex, offsetBy: idx)
116 | let formatChar = phoneFormat[index]
117 | if formatChar != text[index] {
118 | break
119 | }
120 |
121 | if isValuableChar(char: formatChar) {
122 | let newRange = NSRange(location: idx, length: 1)
123 | removeRanges.append(newRange)
124 | }
125 | }
126 | var resultText = unprefixedString
127 | for range in removeRanges.reversed() {
128 | let rangeExpressionStart = resultText.index(resultText.startIndex, offsetBy: range.location)
129 | let rangeExpressionEnd = resultText.index(resultText.startIndex, offsetBy: range.location + 1)
130 | resultText = resultText.replacingCharacters(in: rangeExpressionStart...rangeExpressionEnd, with: "")
131 | }
132 |
133 | return digitOnlyString(text: resultText)
134 | }
135 |
136 | func valuableCharCount(in text: String.SubSequence) -> Int {
137 | let count = text.reduce(0) { (result, item) -> Int in
138 | if isValuableChar(char: item) {
139 | return result + 1
140 | } else {
141 | return result
142 | }
143 | }
144 | return count
145 | }
146 |
147 | private func applyFormat(text: String, format: PhoneFormat, prefix: String?) -> String {
148 | var result: [Character] = []
149 |
150 | var idx = 0
151 | var charIndex = 0
152 | let phoneFormat = format.phoneFormat
153 | while idx < phoneFormat.count && charIndex < text.count {
154 | let index = phoneFormat.index(phoneFormat.startIndex, offsetBy: idx)
155 | let character = phoneFormat[index]
156 | if isRequireSubstitute(char: character) {
157 | let charIndexItem = text.index(text.startIndex, offsetBy: charIndex)
158 | let strp = text[charIndexItem]
159 | charIndex += 1
160 | result.append(strp)
161 | } else {
162 | result.append(character)
163 | }
164 | idx += 1
165 | }
166 | return (prefix ?? "") + String(result)
167 | }
168 |
169 | func pushCaretPosition(text: String?, range: NSRange) -> Int {
170 | guard let text = text else {
171 | return 0
172 | }
173 |
174 | let index = text.index(text.startIndex, offsetBy: range.location + range.length)
175 | let subString = text[index...]
176 | return valuableCharCount(in: subString)
177 | }
178 |
179 | func popCaretPosition(textField: UITextField, range: NSRange, caretPosition: Int)
180 | -> (startPosition: UITextPosition, endPosition: UITextPosition)? {
181 | var currentRange: NSRange = range
182 | if range.length == 0 {
183 | currentRange.length = 1
184 | }
185 |
186 | let text = textField.text ?? ""
187 | var lasts = caretPosition
188 | var start = text.count
189 | var index = start - 1
190 |
191 | while start >= 0 && lasts > 0 {
192 | let indexChar = text.index(text.startIndex, offsetBy: index)
193 | let character = text[indexChar]
194 | if isValuableChar(char: character) {
195 | lasts -= 1
196 | }
197 |
198 | if lasts <= 0 {
199 | start = index
200 | }
201 | index -= 1
202 | }
203 |
204 | if let startPosition = textField.position(from: textField.beginningOfDocument, offset: start),
205 | let endPosition = textField.position(from: startPosition, offset: 0) {
206 | return (startPosition, endPosition)
207 | } else {
208 | return nil
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/PhoneNumberFormatterTests/FormatterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormatterTests.swift
3 | // PhoneNumberFormatterTests
4 | //
5 | // Created by Sergey Shatunov on 9/3/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import PhoneNumberFormatter
11 |
12 | class FormatterTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | }
17 |
18 | override func tearDown() {
19 | super.tearDown()
20 | }
21 |
22 | func testShouldFormatByDefault() {
23 | let defaultFormat = PhoneFormat(defaultPhoneFormat: "+# (###) ###-##-##")
24 | let config = ConfigurationRepo(defaultFormat: defaultFormat)
25 | let inputNumber = "12345678901"
26 |
27 | let formatter = PhoneFormatter(config: config)
28 |
29 | var result = formatter.formatText(text: inputNumber, prefix: nil)
30 | XCTAssert(result.text == "+1 (234) 567-89-01", "Should format correctly")
31 |
32 | formatter.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "+# (###) ###-####")
33 | result = formatter.formatText(text: inputNumber, prefix: nil)
34 | XCTAssert(result.text == "+1 (234) 567-8901", "Should format correctly")
35 | }
36 |
37 | func testShouldDetectSpecificFormats() {
38 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "+# (###) ###-##-##"))
39 | config.add(format: PhoneFormat(phoneFormat: "+### (##) ###-##-##", regexp: "^380\\d*$"))
40 |
41 | let formatter = PhoneFormatter(config: config)
42 |
43 | let inputNumber = "12345678901"
44 | let specififcInputNumber = "38012345678901"
45 |
46 | var result = formatter.formatText(text: inputNumber, prefix: nil)
47 | XCTAssert(result.text == "+1 (234) 567-89-01", "Should format number by default")
48 |
49 | result = formatter.formatText(text: specififcInputNumber, prefix: nil)
50 | XCTAssert(result.text == "+380 (12) 345-67-89", "specififcInputNumber")
51 | }
52 |
53 | func testShouldHandleSpecialSymbols() {
54 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "+# (###) ###-##-##"))
55 | let formatter = PhoneFormatter(config: config)
56 |
57 | var inputNumber = "!#dsti*&"
58 | var result = formatter.formatText(text: inputNumber, prefix: nil)
59 | XCTAssert(result.text == "", "should remove non-number symbols")
60 |
61 | inputNumber = "+12345678901"
62 | result = formatter.formatText(text: inputNumber, prefix: nil)
63 | XCTAssert(result.text == "+1 (234) 567-89-01", "should format number by default and handle + symbol")
64 | }
65 |
66 | func testShouldHandleFormatWithDigitsAtStart() {
67 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "+7 (###) ###-##-##"))
68 | let formatter = PhoneFormatter(config: config)
69 |
70 | var inputNumber = "9201234567"
71 | var result = formatter.formatText(text: inputNumber, prefix: nil)
72 | XCTAssert(result.text == "+7 (920) 123-45-67", "should format correctly")
73 |
74 | inputNumber = "7777778877"
75 | result = formatter.formatText(text: inputNumber, prefix: nil)
76 | XCTAssert(result.text == "+7 (777) 777-88-77", "should format correctly")
77 | }
78 |
79 | func testShouldHandleFormatWithDigitsInTheMiddle() {
80 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "### 123 ##-##"))
81 | let formatter = PhoneFormatter(config: config)
82 |
83 | var inputNumber = "3211231"
84 | var result = formatter.formatText(text: inputNumber, prefix: nil)
85 | XCTAssert(result.text == "321 123 12-31", "should format correctly")
86 |
87 | inputNumber = "1113333"
88 | result = formatter.formatText(text: inputNumber, prefix: nil)
89 | XCTAssert(result.text == "111 123 33-33", "should format correctly")
90 | }
91 |
92 | func testShouldCheckPrefix() {
93 | let prefix = "pr3f1x"
94 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "(###) ###-##-##"))
95 | let formatter = PhoneFormatter(config: config)
96 |
97 | let inputNumber = "9201234567"
98 | let result = formatter.formatText(text: inputNumber, prefix: prefix)
99 | XCTAssert(result.text == prefix + "(920) 123-45-67", "should format correctly")
100 | }
101 |
102 | func testShouldCheckPrefixAndDifferentFormats() {
103 | let prefix = "pr3-f1x"
104 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "##########"))
105 | config.add(format: PhoneFormat(phoneFormat: "+### (##) ###-##-##", regexp: "^380\\d*$"))
106 | config.add(format: PhoneFormat(phoneFormat: "+### (##) ###-##-##", regexp: "^123\\d*$"))
107 | let formatter = PhoneFormatter(config: config)
108 |
109 | let inputNumber = "3801234567"
110 | let inputNumberNonImage = "1231234567"
111 | var result = formatter.formatText(text: inputNumber, prefix: prefix)
112 | XCTAssert(result.text == prefix + "+380 (12) 345-67", "should format correctly")
113 |
114 | result = formatter.formatText(text: inputNumberNonImage, prefix: prefix)
115 | XCTAssert(result.text == prefix + "+123 (12) 345-67", "should format correctly")
116 | }
117 |
118 | func testShouldHandleNumberFormatStyles() {
119 | let prefix: String? = nil
120 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "+78 (###) ###-##-##"))
121 | let formatter = PhoneFormatter(config: config)
122 |
123 | var result = formatter.formatText(text: "+7 (123", prefix: prefix)
124 | XCTAssert(result.text == "+78 (123", "should format correctly")
125 |
126 | result = formatter.formatText(text: "+87 (1234", prefix: prefix)
127 | XCTAssert(result.text == "+78 (871) 234", "should format correctly")
128 |
129 | formatter.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "+7 (###) 88#-##-##")
130 |
131 | result = formatter.formatText(text: "+7 (123", prefix: prefix)
132 | XCTAssert(result.text == "+7 (123", "should format correctly")
133 |
134 | result = formatter.formatText(text: "1234", prefix: prefix)
135 | XCTAssert(result.text == "+7 (123) 884", "should format correctly")
136 |
137 | result = formatter.formatText(text: "+7 (123) 884", prefix: prefix)
138 | XCTAssert(result.text == "+7 (123) 888-84", "should format correctly")
139 |
140 | result = formatter.formatText(text: "+7 (123) 8887", prefix: prefix)
141 | XCTAssert(result.text == "+7 (123) 888-88-7", "should format correctly")
142 | }
143 |
144 | func testShouldHandlePrefixNumberFormatStyles() {
145 | let prefix: String = "pr3-f1x "
146 | let config = ConfigurationRepo(defaultFormat: PhoneFormat(defaultPhoneFormat: "+7 (###) 88#-##-##"))
147 | let formatter = PhoneFormatter(config: config)
148 |
149 | var result = formatter.formatText(text: "+7 (123", prefix: prefix)
150 | XCTAssert(result.text == prefix + "+7 (123", "should format correctly")
151 |
152 | result = formatter.formatText(text: "+7 (123) 8887", prefix: prefix)
153 | XCTAssert(result.text == prefix + "+7 (123) 888-88-7", "should format correctly")
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/PhoneNumberFormatterTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PhoneNumberFormatterTests/TextFieldTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldTests.swift
3 | // PhoneNumberFormatterTests
4 | //
5 | // Created by Sergey Shatunov on 9/3/17.
6 | // Copyright © 2017 SHS. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import PhoneNumberFormatter
11 |
12 | class TextFieldTests: XCTestCase {
13 |
14 | func testShouldSetFormattedText() {
15 | let textField = PhoneFormattedTextField(frame: CGRect.zero)
16 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "+# (###) ###-##-##")
17 | textField.formattedText = "12312312323555555"
18 | XCTAssert(textField.text == "+1 (231) 231-23-23", "Should be formatted")
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Phone Number Formatter
2 |
3 | [](https://travis-ci.org/Serheo/PhoneNumberFormatter)
4 | 
5 | [](https://github.com/Carthage/Carthage)
6 |
7 | UITextField and NSFormatter subclasses for formatting phone numbers. Allow different formats for different countries(patterns). Caret positioning works excellent.
8 | Swift 4.
9 | If you need ObjC support use - https://github.com/Serheo/SHSPhoneComponent/
10 |
11 | ## Installation
12 |
13 | #### Carthage
14 | ```
15 | github "Serheo/PhoneNumberFormatter"
16 | ```
17 | #### CocoaPods
18 | ```
19 | pod "SwiftPhoneNumberFormatter"
20 | ```
21 |
22 | ## Getting Started
23 |
24 | ### Default Format
25 | ```swift
26 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "(###) ###-##-##")
27 | ```
28 | All input strings will be parsed in that way.
29 | Example: +1 (123) 123-45-67
30 |
31 |
32 |
33 |
34 |
35 | ### Prefix Format
36 | You can set prefix on all inputs:
37 | ```swift
38 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "(###) ###-##-##")
39 | textField.prefix = "+7 "
40 | ```
41 |
42 | ### Multiple Formats
43 |
44 | ```swift
45 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "##########")
46 | textField.prefix = nil
47 | let custom1 = PhoneFormat(phoneFormat: "+# (###) ###-##-##", regexp: "^7[0-689]\\d*$")
48 | textField.config.add(format: custom1)
49 |
50 | let custom2 = PhoneFormat(phoneFormat: "+### ###-##-##", regexp: "^380\\d*$")
51 | textField.config.add(format: custom2)
52 | ```
53 |
54 | ### Multiple Formats with prefix
55 |
56 | ```swift
57 | textField.config.defaultConfiguration = PhoneFormat(defaultPhoneFormat: "### ### ###")
58 | textField.prefix = "+7 "
59 |
60 | let custom1 = PhoneFormat(phoneFormat: "(###) ###-##-##", regexp: "^1\\d*$")
61 | textField.config.add(format: custom1)
62 |
63 | let custom2 = PhoneFormat(phoneFormat: "(###) ###-###", regexp: "^2\\d*$")
64 | textField.config.add(format: custom2)
65 | ```
66 |
67 | ### Listening to changes
68 | To be notified of changes on the textField input add a `textDidChangeBlock` closure
69 | ```swift
70 | textField.textDidChangeBlock = { field in
71 | if let text = field?.text, text != "" {
72 | print(text)
73 | } else {
74 | print("No text")
75 | }
76 | ```
77 |
78 | Attempting to listen to changes through other means will likely fail (e.g., implementing `UITextFieldDelegate`'s `textField:shouldChangeCharactersIn:range:`).
79 |
80 | ## Requirements
81 | iOS 9+
82 | Swift 4
83 |
84 | ## License
85 | PhoneNumberFormatter is available under the MIT license. See the LICENSE file for more info.
86 |
--------------------------------------------------------------------------------
/SwiftPhoneNumberFormatter.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "SwiftPhoneNumberFormatter"
3 | s.version = "1.5"
4 | s.summary = "PhoneNumberFormatter for iOS"
5 | s.homepage = "https://github.com/Serheo/PhoneNumberFormatter"
6 | s.license = 'MIT'
7 | s.author = { "Sergei Shatunov" => "sshatunov@gmail.com" }
8 | s.source = { :git => "https://github.com/Serheo/PhoneNumberFormatter.git", :tag => s.version.to_s }
9 | s.platform = :ios, '9.0'
10 | s.source_files = 'PhoneNumberFormatter/Sources/**/*.{swift}'
11 | s.requires_arc = true
12 | end
--------------------------------------------------------------------------------