├── .gitignore
├── Documentation
├── CardEntryDemo.gif
├── EmailFormatter.gif
└── PhoneFormatter.gif
├── Example
├── FrictionLess.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── FrictionLess-Example.xcscheme
├── FrictionLess.xcworkspace
│ └── contents.xcworkspacedata
├── FrictionLess
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ └── LaunchScreen.xib
│ ├── CardEntryExampleViewController.swift
│ ├── ExampleMenuViewController.swift
│ ├── FormattableTextFieldExampleViewController.swift
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ └── Info.plist
├── Podfile
├── Podfile.lock
├── Pods
│ ├── Anchorage
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── Source
│ │ │ ├── AnchorGroupProvider.swift
│ │ │ ├── Anchorage.swift
│ │ │ ├── Compatability.swift
│ │ │ ├── Internal.swift
│ │ │ └── Priority.swift
│ ├── Local Podspecs
│ │ └── FrictionLess.podspec.json
│ ├── Manifest.lock
│ ├── PhoneNumberKit
│ │ ├── LICENSE
│ │ ├── PhoneNumberKit
│ │ │ ├── Constants.swift
│ │ │ ├── Formatter.swift
│ │ │ ├── MetadataManager.swift
│ │ │ ├── MetadataTypes.swift
│ │ │ ├── NSRegularExpression+Swift.swift
│ │ │ ├── ParseManager.swift
│ │ │ ├── PartialFormatter.swift
│ │ │ ├── PhoneNumber.swift
│ │ │ ├── PhoneNumberKit.swift
│ │ │ ├── PhoneNumberParser.swift
│ │ │ ├── RegexManager.swift
│ │ │ ├── Resources
│ │ │ │ └── PhoneNumberMetadata.json
│ │ │ └── UI
│ │ │ │ └── TextField.swift
│ │ └── README.md
│ ├── Pods.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── FrictionLess.xcscheme
│ └── Target Support Files
│ │ ├── Anchorage
│ │ ├── Anchorage-dummy.m
│ │ ├── Anchorage-prefix.pch
│ │ ├── Anchorage-umbrella.h
│ │ ├── Anchorage.modulemap
│ │ ├── Anchorage.xcconfig
│ │ └── Info.plist
│ │ ├── FrictionLess
│ │ ├── FrictionLess-dummy.m
│ │ ├── FrictionLess-prefix.pch
│ │ ├── FrictionLess-umbrella.h
│ │ ├── FrictionLess.modulemap
│ │ ├── FrictionLess.xcconfig
│ │ └── Info.plist
│ │ ├── PhoneNumberKit
│ │ ├── Info.plist
│ │ ├── PhoneNumberKit-dummy.m
│ │ ├── PhoneNumberKit-prefix.pch
│ │ ├── PhoneNumberKit-umbrella.h
│ │ ├── PhoneNumberKit.modulemap
│ │ └── PhoneNumberKit.xcconfig
│ │ ├── Pods-FrictionLess_Example
│ │ ├── Info.plist
│ │ ├── Pods-FrictionLess_Example-acknowledgements.markdown
│ │ ├── Pods-FrictionLess_Example-acknowledgements.plist
│ │ ├── Pods-FrictionLess_Example-dummy.m
│ │ ├── Pods-FrictionLess_Example-frameworks.sh
│ │ ├── Pods-FrictionLess_Example-resources.sh
│ │ ├── Pods-FrictionLess_Example-umbrella.h
│ │ ├── Pods-FrictionLess_Example.debug.xcconfig
│ │ ├── Pods-FrictionLess_Example.modulemap
│ │ └── Pods-FrictionLess_Example.release.xcconfig
│ │ └── Pods-FrictionLess_Tests
│ │ ├── Info.plist
│ │ ├── Pods-FrictionLess_Tests-acknowledgements.markdown
│ │ ├── Pods-FrictionLess_Tests-acknowledgements.plist
│ │ ├── Pods-FrictionLess_Tests-dummy.m
│ │ ├── Pods-FrictionLess_Tests-frameworks.sh
│ │ ├── Pods-FrictionLess_Tests-resources.sh
│ │ ├── Pods-FrictionLess_Tests-umbrella.h
│ │ ├── Pods-FrictionLess_Tests.debug.xcconfig
│ │ ├── Pods-FrictionLess_Tests.modulemap
│ │ └── Pods-FrictionLess_Tests.release.xcconfig
└── Tests
│ ├── CardStateTests.swift
│ ├── CardTypeTests.swift
│ ├── CreditCardFormatterTests.swift
│ ├── ExpirationDateFormatterTests.swift
│ ├── FormattableTextFieldTestHelpers.swift
│ └── Info.plist
├── FrictionLess.podspec
├── FrictionLess
├── CardEntry
│ ├── CardEntry.strings
│ ├── CardEntry.xcassets
│ │ ├── CameraScan.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-scan.pdf
│ │ ├── Contents.json
│ │ └── Credit Card
│ │ │ ├── CVV
│ │ │ ├── Contents.json
│ │ │ ├── back.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── icn-cc-back-ccv.pdf
│ │ │ └── front.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── icn-cc-amex-ccv.pdf
│ │ │ ├── Contents.json
│ │ │ ├── americanexpress.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-amex.pdf
│ │ │ ├── diners.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-dinersclub.pdf
│ │ │ ├── discover.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-discover.pdf
│ │ │ ├── jcb.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-jcb.pdf
│ │ │ ├── mastercard.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-mastercard.pdf
│ │ │ ├── notAccepted.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-placeholder-not-accepted.pdf
│ │ │ ├── placeholder.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-placeholder.pdf
│ │ │ └── visa.imageset
│ │ │ ├── Contents.json
│ │ │ └── icn-cc-visa.pdf
│ ├── CardEntryView.swift
│ ├── CardEntryViewController.swift
│ ├── CardImageViewModel.swift
│ ├── CardParser.swift
│ ├── CreditEntryViewState.swift
│ ├── Formatters
│ │ ├── CVVFormatter.swift
│ │ ├── CreditCardFormatter.swift
│ │ ├── ExpirationDateFormatter.swift
│ │ └── ZipFormatter.swift
│ ├── README.md
│ ├── Strings+CardEntry.swift
│ └── UIImage+CardEntry.swift
├── FormUI
│ ├── FormComponent.swift
│ ├── FormUI.strings
│ ├── FrictionLessFormComponent.swift
│ ├── FrictionLessFormUIStrings.swift
│ └── README.md
├── FormattableTextField
│ ├── FormattableTextField.swift
│ ├── FormattableTextFieldDelegate.swift
│ ├── Formatters
│ │ ├── EmailFormatter.swift
│ │ └── NameFormatter.swift
│ ├── README.md
│ ├── TextFieldFormatter.swift
│ ├── TextFieldFormatterDataTypes.swift
│ └── Utilities
│ │ ├── FormattableTextField+LayoutMargins.swift
│ │ ├── RangeHelpers.swift
│ │ ├── String+CursorFingerprint.swift
│ │ └── String+Filtering.swift
└── PhoneFormatter
│ ├── PhoneFormatter.swift
│ └── README.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── _Pods.xcodeproj
├── circle.yml
└── scripts
└── iOS.sh
/.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 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
--------------------------------------------------------------------------------
/Documentation/CardEntryDemo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/Documentation/CardEntryDemo.gif
--------------------------------------------------------------------------------
/Documentation/EmailFormatter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/Documentation/EmailFormatter.gif
--------------------------------------------------------------------------------
/Documentation/PhoneFormatter.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/Documentation/PhoneFormatter.gif
--------------------------------------------------------------------------------
/Example/FrictionLess.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/FrictionLess.xcodeproj/xcshareddata/xcschemes/FrictionLess-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
80 |
86 |
87 |
88 |
89 |
90 |
91 |
97 |
99 |
105 |
106 |
107 |
108 |
110 |
111 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/Example/FrictionLess.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/FrictionLess/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // FrictionLess
4 | //
5 | // Created by jason.clark@raizlabs.com on 07/17/2017.
6 | // Copyright (c) 2017 jason.clark@raizlabs.com. 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, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 |
19 | window = UIWindow()
20 | window?.backgroundColor = .white
21 | let nav = UINavigationController(rootViewController: ExampleMenuViewController())
22 | window?.rootViewController = nav
23 | window?.makeKeyAndVisible()
24 |
25 | return true
26 | }
27 |
28 | func applicationWillResignActive(_ application: UIApplication) {
29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
30 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
31 | }
32 |
33 | func applicationDidEnterBackground(_ application: UIApplication) {
34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
36 | }
37 |
38 | func applicationWillEnterForeground(_ application: UIApplication) {
39 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
40 | }
41 |
42 | func applicationDidBecomeActive(_ application: UIApplication) {
43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
44 | }
45 |
46 | func applicationWillTerminate(_ application: UIApplication) {
47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Example/FrictionLess/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Example/FrictionLess/CardEntryExampleViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardEntryExampleViewController.swift
3 | // FrictionLess
4 | //
5 | // Created by jason.clark@raizlabs.com on 07/17/2017.
6 | // Copyright (c) 2017 jason.clark@raizlabs.com. All rights reserved.
7 | //
8 |
9 | import FrictionLess
10 | import Anchorage
11 |
12 | class CardEntryExampleViewController: UIViewController, UITextFieldDelegate {
13 |
14 | var doneButton: UIBarButtonItem?
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | title = "Card Entry"
19 |
20 | doneButton = UIBarButtonItem(barButtonSystemItem: .done,
21 | target: self,
22 | action: #selector(doneButtonPressed))
23 | navigationItem.rightBarButtonItem = doneButton
24 | doneButton?.isEnabled = false
25 | }
26 |
27 | override func loadView() {
28 | view = UIView()
29 | view.backgroundColor = UIColor.white.withAlphaComponent(0.9)
30 |
31 | let form = CardEntryViewController()
32 | form.delegate = self
33 | view.addSubview(form.view)
34 | addChildViewController(form)
35 | form.didMove(toParentViewController: self)
36 |
37 | form.view.topAnchor == view.layoutMarginsGuide.topAnchor + 80
38 | form.view.horizontalAnchors == view.layoutMarginsGuide.horizontalAnchors
39 | style(cardEntryView: form.cardEntryView)
40 | }
41 |
42 | }
43 |
44 | extension CardEntryExampleViewController: CardEntryViewControllderDelegate {
45 |
46 | func cardEntryViewController(_ vc: CardEntryViewController, creditCardValid: Bool) {
47 | doneButton?.isEnabled = creditCardValid
48 | }
49 |
50 | }
51 |
52 | fileprivate extension CardEntryExampleViewController {
53 |
54 | func style(cardEntryView view: CardEntryView) {
55 | let fieldAppearance = FormattableTextField.appearance()
56 | fieldAppearance.backgroundColor = .white
57 | fieldAppearance.cornerRadius = 5
58 |
59 | let componentAppearance = FrictionLessFormComponent.appearance()
60 | componentAppearance.titleToTextFieldPadding = 3
61 |
62 | let validationAppearance = FrictionLessFormValidationLabel.appearance()
63 | validationAppearance.textColor = .red
64 |
65 | view.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
66 | view.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 10, right: 20)
67 | view.layer.cornerRadius = 10
68 | }
69 |
70 | @objc func doneButtonPressed() {
71 | let alert = UIAlertController(title: "Card Submitted",
72 | message: nil,
73 | preferredStyle: .alert)
74 | alert.addAction(UIAlertAction(title: "Hooray!", style: UIAlertActionStyle.default))
75 | present(alert, animated: true, completion: nil)
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/Example/FrictionLess/ExampleMenuViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleMenuViewController.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 7/27/17.
6 | // Copyright © 2017 CocoaPods. All rights reserved.
7 | //
8 |
9 | import Anchorage
10 |
11 | final class ExampleMenuViewController: UIViewController {
12 |
13 | typealias MenuItem = (title: String, ViewControllerType: UIViewController.Type)
14 |
15 | let dataSource: [MenuItem] = [
16 | ("FormattableTextField", FormattableTextFieldExampleViewController.self),
17 | ("Card Entry", CardEntryExampleViewController.self),
18 | ]
19 |
20 | let reuseID = "\(UITableViewCell.self)"
21 | let tableView = UITableView()
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 | title = "FrictionLess"
26 |
27 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseID)
28 | tableView.dataSource = self
29 | tableView.delegate = self
30 | tableView.tableFooterView = UIView()
31 | }
32 |
33 | override func loadView() {
34 | view = UIView()
35 | view.addSubview(tableView)
36 | tableView.edgeAnchors == view.edgeAnchors
37 | }
38 |
39 | }
40 |
41 | extension ExampleMenuViewController: UITableViewDataSource {
42 |
43 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
44 | return dataSource.count
45 | }
46 |
47 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
48 | let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath)
49 | cell.accessoryType = .disclosureIndicator
50 | cell.textLabel?.text = dataSource[indexPath.row].title
51 | return cell
52 | }
53 |
54 | }
55 |
56 | extension ExampleMenuViewController: UITableViewDelegate {
57 |
58 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
59 | let vc = dataSource[indexPath.row].ViewControllerType.init()
60 | self.navigationController?.pushViewController(vc, animated: true)
61 | tableView.deselectRow(at: indexPath, animated: true)
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/Example/FrictionLess/Images.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 | "info" : {
45 | "version" : 1,
46 | "author" : "xcode"
47 | }
48 | }
--------------------------------------------------------------------------------
/Example/FrictionLess/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '10.0'
2 | use_frameworks!
3 |
4 | target 'FrictionLess_Example' do
5 | pod 'FrictionLess', :path => '../'
6 | pod 'Anchorage'
7 |
8 | target 'FrictionLess_Tests' do
9 | inherit! :search_paths
10 |
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Anchorage (4.0.0)
3 | - FrictionLess (0.1.0):
4 | - FrictionLess/All (= 0.1.0)
5 | - FrictionLess/All (0.1.0):
6 | - FrictionLess/CardEntry
7 | - FrictionLess/FormattableTextField
8 | - FrictionLess/FormUI
9 | - FrictionLess/PhoneFormatter
10 | - FrictionLess/CardEntry (0.1.0):
11 | - FrictionLess/FormUI
12 | - FrictionLess/FormattableTextField (0.1.0)
13 | - FrictionLess/FormUI (0.1.0):
14 | - Anchorage
15 | - FrictionLess/FormattableTextField
16 | - FrictionLess/PhoneFormatter (0.1.0):
17 | - FrictionLess/FormattableTextField
18 | - PhoneNumberKit
19 | - PhoneNumberKit (1.3.0):
20 | - PhoneNumberKit/PhoneNumberKitCore (= 1.3.0)
21 | - PhoneNumberKit/UIKit (= 1.3.0)
22 | - PhoneNumberKit/PhoneNumberKitCore (1.3.0)
23 | - PhoneNumberKit/UIKit (1.3.0):
24 | - PhoneNumberKit/PhoneNumberKitCore
25 |
26 | DEPENDENCIES:
27 | - Anchorage
28 | - FrictionLess (from `../`)
29 |
30 | EXTERNAL SOURCES:
31 | FrictionLess:
32 | :path: ../
33 |
34 | SPEC CHECKSUMS:
35 | Anchorage: fe50dca4e20846b8cb15c859d26fec5132b41577
36 | FrictionLess: 67e8ba3405cf5dfda91070f391cbcace578be7b6
37 | PhoneNumberKit: 08aaf1968ea41a114327e9223cbb0391d76ae145
38 |
39 | PODFILE CHECKSUM: f208593ae0b01d05e3b9f88ac99038748963b697
40 |
41 | COCOAPODS: 1.2.1
42 |
--------------------------------------------------------------------------------
/Example/Pods/Anchorage/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Raizlabs
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 |
--------------------------------------------------------------------------------
/Example/Pods/Anchorage/README.md:
--------------------------------------------------------------------------------
1 | # Anchorage
2 |
3 | [](https://swift.org)
4 | []()
5 | [](https://cocoadocs.org/docsets/Anchorage)
6 | [](http://cocoapods.org/pods/Anchorage)
7 | [](https://github.com/Carthage/Carthage)
8 |
9 | A lightweight collection of intuitive operators and utilities that simplify Auto Layout code. Anchorage is built directly on top of the `NSLayoutAnchor` API.
10 |
11 | Each expression acts on one or more `NSLayoutAnchor`s, and returns active `NSLayoutConstraint`s. If you want inactive constraints, [here's how to do that](#batching-constraints).
12 |
13 | # Usage
14 |
15 | ## Alignment
16 |
17 | ```swift
18 | // Pin the button to 12 pt from the leading edge of its container
19 | button.leadingAnchor == container.leadingAnchor + 12
20 |
21 | // Pin the button to at least 12 pt from the trailing edge of its container
22 | button.trailingAnchor <= container.leadingAnchor - 12
23 |
24 | // Center one or both axes of a view
25 | button.centerXAnchor == container.centerXAnchor
26 | button.centerAnchors == container.centerAnchors
27 | ```
28 | ## Sizing
29 |
30 | ```swift
31 | // Constrain a view's width to be at most 100 pt
32 | view.widthAnchor <= 100
33 |
34 | // Constraint a view to a fixed size
35 | imageView.sizeAnchors == CGSize(width: 100, height: 200)
36 |
37 | // Constrain two views to be the same size
38 | imageView.sizeAnchors == view.sizeAnchors
39 |
40 | // Constrain view to 4:3 aspect ratio
41 | view.widthAnchor == 4 * view.heightAnchor / 3
42 | ```
43 |
44 | ## Composite Anchors
45 |
46 | Constrain multiple edges at a time with this syntax:
47 |
48 | ```swift
49 | // Constrain the leading, trailing, top and bottom edges to be equal
50 | imageView.edgeAnchors == container.edgeAnchors
51 |
52 | // Inset the edges of a view from another view
53 | let insets = UIEdgeInsets(top: 5, left: 10, bottom: 15, right: 20)
54 | imageView.edgeAnchors == container.edgeAnchors + insets
55 |
56 | // Inset the leading and trailing anchors by 10
57 | imageView.horizontalAnchors >= container.horizontalAnchors + 10
58 |
59 | // Inset the top and bottom anchors by 10
60 | imageView.verticalAnchors >= container.verticalAnchors + 10
61 | ```
62 |
63 | #### Use leading and trailing
64 | Using `leftAnchor` and `rightAnchor` is rarely the right choice. To encourage this, `horizontalAnchors` and `edgeAnchors` use the `leadingAnchor` and `trailingAnchor` layout anchors.
65 |
66 | #### Inset instead of Shift
67 | When constraining leading/trailing or top/bottom, it is far more common to work in terms of an inset from the edges instead of shifting both edges in the same direction. When building the expression, Anchorage will flip the relationship and invert the constant in the constraint on the far side of the axis. This makes the expressions much more natural to work with.
68 |
69 |
70 | ## Priority
71 |
72 | The `~` is used to specify priority of the constraint resulting from any Anchorage expression:
73 |
74 | ```swift
75 | // Align view 20 points from the center of its superview, with system-defined low priority
76 | view.centerXAnchor == view.superview.centerXAnchor + 20 ~ .low
77 |
78 | // Align view 20 points from the center of its superview, with (required - 1) priority
79 | view.centerXAnchor == view.superview.centerXAnchor + 20 ~ .required - 1
80 |
81 | // Align view 20 points from the center of its superview, with custom priority
82 | view.centerXAnchor == view.superview.centerXAnchor + 20 ~ 752
83 | ```
84 | The layout priority is an enum with the following values:
85 |
86 | - `.required` - `UILayoutPriorityRequired` (default)
87 | - `.high` - `UILayoutPriorityDefaultHigh`
88 | - `.low` - `UILayoutPriorityDefaultLow`
89 | - `.fittingSize` - `UILayoutPriorityFittingSizeLevel`
90 |
91 | ## Storing Constraints
92 |
93 | To store constraints created by Anchorage, simply assign the expression to a variable:
94 |
95 | ```swift
96 | // A single (active) NSLayoutConstraint
97 | let topConstraint = (imageView.topAnchor == container.topAnchor)
98 |
99 | // EdgeConstraints represents a collection of constraints
100 | // You can retrieve the NSLayoutConstraints individually,
101 | // or get an [NSLayoutConstraint] via .all, .horizontal, or .vertical
102 | let edgeConstraints = (button.edgeAnchors == container.edgeAnchors).all
103 | ```
104 |
105 | ## Batching Constraints
106 |
107 | By default, Anchorage returns active layout constraints. If you'd rather return inactive constraints for use with the [`NSLayoutConstraint.activate(_:)` method](https://developer.apple.com/reference/uikit/nslayoutconstraint/1526955-activate) for performance reasons, you can do it like this:
108 |
109 | ```swift
110 | let constraints = Anchorage.batch(active: false) {
111 | view1.widthAnchor == view2.widthAnchor
112 | view1.heightAnchor == view2.heightAnchor / 2 ~ .low
113 | // ... as many constraints as you want
114 | }
115 |
116 | // Later:
117 | NSLayoutConstraint.activate(constraints)
118 | ```
119 |
120 | You can also pass `active: true` if you want the constraints in the array to be automatically activated in a batch.
121 |
122 | ## Autoresizing Mask
123 |
124 | Anchorage sets the `translatesAutoresizingMaskIntoConstraints` property to `false` on the *left* hand side of the expression, so you should never need to set this property manually. This is important to be aware of in case the container view relies on `translatesAutoresizingMaskIntoConstraints` being set to `true`. We tend to keep child views on the left hand side of the expression to avoid this problem, especially when constraining to a system-supplied view.
125 |
126 | # Installation
127 |
128 | ## CocoaPods
129 |
130 | To integrate Anchorage into your Xcode project using CocoaPods, specify it in
131 | your Podfile:
132 |
133 | ```ruby
134 | pod 'Anchorage'
135 | ```
136 |
137 | ## Carthage
138 |
139 | To integrate Anchorage into your Xcode project using Carthage, specify it in
140 | your Cartfile:
141 |
142 | ```
143 | github "Raizlabs/Anchorage" ~> 3.0
144 | ```
145 |
146 | Run `carthage update` to build the framework and drag the built
147 | `Anchorage.framework` into your Xcode project.
148 |
149 | # License
150 |
151 | This code and tool is under the MIT License. See `LICENSE` file in this repository.
152 |
153 | Any ideas and contributions welcome!
154 |
--------------------------------------------------------------------------------
/Example/Pods/Anchorage/Source/AnchorGroupProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnchorGroupProvider.swift
3 | // Anchorage
4 | //
5 | // Created by Rob Visentin on 5/1/17.
6 | //
7 | // Copyright 2016 Raizlabs and other contributors
8 | // http://raizlabs.com/
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining
11 | // a copy of this software and associated documentation files (the
12 | // Software"), to deal in the Software without restriction, including
13 | // without limitation the rights to use, copy, modify, merge, publish,
14 | // distribute, sublicense, and/or sell copies of the Software, and to
15 | // permit persons to whom the Software is furnished to do so, subject to
16 | // the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be
19 | // included in all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 |
29 | #if os(macOS)
30 | import Cocoa
31 | #else
32 | import UIKit
33 | #endif
34 |
35 | public protocol AnchorGroupProvider {
36 |
37 | var horizontalAnchors: AnchorPair { get }
38 | var verticalAnchors: AnchorPair { get }
39 | var centerAnchors: AnchorPair { get }
40 | var sizeAnchors: AnchorPair { get }
41 |
42 | }
43 |
44 | extension AnchorGroupProvider {
45 |
46 | public var edgeAnchors: EdgeAnchors {
47 | return EdgeAnchors(horizontal: horizontalAnchors, vertical: verticalAnchors)
48 | }
49 |
50 | }
51 |
52 | extension View: AnchorGroupProvider {
53 |
54 | public var horizontalAnchors: AnchorPair {
55 | return AnchorPair(first: leadingAnchor, second: trailingAnchor)
56 | }
57 |
58 | public var verticalAnchors: AnchorPair {
59 | return AnchorPair(first: topAnchor, second: bottomAnchor)
60 | }
61 |
62 | public var centerAnchors: AnchorPair {
63 | return AnchorPair(first: centerXAnchor, second: centerYAnchor)
64 | }
65 |
66 | public var sizeAnchors: AnchorPair {
67 | return AnchorPair(first: widthAnchor, second: heightAnchor)
68 | }
69 |
70 | }
71 |
72 | extension ViewController: AnchorGroupProvider {
73 |
74 | public var horizontalAnchors: AnchorPair {
75 | return view.horizontalAnchors
76 | }
77 |
78 | public var verticalAnchors: AnchorPair {
79 | #if os(macOS)
80 | return view.verticalAnchors
81 | #else
82 | return AnchorPair(first: topLayoutGuide.bottomAnchor, second: bottomLayoutGuide.topAnchor)
83 | #endif
84 | }
85 |
86 | public var centerAnchors: AnchorPair {
87 | return view.centerAnchors
88 | }
89 |
90 | public var sizeAnchors: AnchorPair {
91 | return view.sizeAnchors
92 | }
93 |
94 | }
95 |
96 | extension LayoutGuide: AnchorGroupProvider {
97 |
98 | public var horizontalAnchors: AnchorPair {
99 | return AnchorPair(first: leadingAnchor, second: trailingAnchor)
100 | }
101 |
102 | public var verticalAnchors: AnchorPair {
103 | return AnchorPair(first: topAnchor, second: bottomAnchor)
104 | }
105 |
106 | public var centerAnchors: AnchorPair {
107 | return AnchorPair(first: centerXAnchor, second: centerYAnchor)
108 | }
109 |
110 | public var sizeAnchors: AnchorPair {
111 | return AnchorPair(first: widthAnchor, second: heightAnchor)
112 | }
113 |
114 | }
115 |
116 | // MARK: - EdgeAnchors
117 |
118 | public struct EdgeAnchors: LayoutAnchorType {
119 |
120 | public var horizontalAnchors: AnchorPair
121 | public var verticalAnchors: AnchorPair
122 |
123 | }
124 |
125 | // MARK: - Axis Group
126 |
127 | public struct ConstraintPair {
128 |
129 | public var first: NSLayoutConstraint
130 | public var second: NSLayoutConstraint
131 |
132 | }
133 |
134 | // MARK: - ConstraintGroup
135 |
136 | public struct ConstraintGroup {
137 |
138 | public var top: NSLayoutConstraint
139 | public var leading: NSLayoutConstraint
140 | public var bottom: NSLayoutConstraint
141 | public var trailing: NSLayoutConstraint
142 |
143 | public var horizontal: [NSLayoutConstraint] {
144 | return [leading, trailing]
145 | }
146 |
147 | public var vertical: [NSLayoutConstraint] {
148 | return [top, bottom]
149 | }
150 |
151 | public var all: [NSLayoutConstraint] {
152 | return [top, leading, bottom, trailing]
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/Example/Pods/Anchorage/Source/Compatability.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Compatability.swift
3 | // Anchorage
4 | //
5 | // Created by Rob Visentin on 5/1/17.
6 | //
7 | // Copyright 2016 Raizlabs and other contributors
8 | // http://raizlabs.com/
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining
11 | // a copy of this software and associated documentation files (the
12 | // Software"), to deal in the Software without restriction, including
13 | // without limitation the rights to use, copy, modify, merge, publish,
14 | // distribute, sublicense, and/or sell copies of the Software, and to
15 | // permit persons to whom the Software is furnished to do so, subject to
16 | // the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be
19 | // included in all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 |
29 | #if os(macOS)
30 | import Cocoa
31 |
32 | public typealias LayoutPriority = NSLayoutPriority
33 | #else
34 | import UIKit
35 |
36 | public typealias LayoutPriority = UILayoutPriority
37 | public typealias EdgeInsets = UIEdgeInsets
38 | #endif
39 |
40 | public extension BinaryFloatingPoint {
41 |
42 | public static var exponentBias: Int {
43 | return (1 << (Self.exponentBitCount - 1)) - 1
44 | }
45 |
46 | public static var exponentMax: Int {
47 | return (1 << exponentBitCount) - 1
48 | }
49 |
50 | public init(_ value: T) {
51 | assert(Self.radix == T.radix)
52 |
53 | let pattern: (exp: UIntMax, sig: UIntMax)
54 |
55 | switch value.floatingPointClass {
56 | case .positiveZero, .negativeZero:
57 | pattern = (exp: 0, sig: 0)
58 | case .positiveInfinity, .negativeInfinity:
59 | pattern = (exp: UIntMax(bitPattern: IntMax(Self.exponentMax)), sig: 0)
60 | case .signalingNaN:
61 | pattern = (exp: UIntMax(bitPattern: IntMax(Self.exponentMax)), sig: 1)
62 | case .quietNaN:
63 | pattern = (exp: UIntMax(bitPattern: IntMax(Self.exponentMax)), sig: UIntMax(bitPattern: IntMax(1 << (Self.significandBitCount - 1))))
64 | default:
65 | pattern.exp = UIntMax(bitPattern: value.exponent.toIntMax() + IntMax(Self.exponentBias))
66 |
67 | let sig = value.significandBitPattern.toUIntMax()
68 | if Self.significandBitCount >= T.significandBitCount {
69 | pattern.sig = sig << UIntMax(bitPattern: IntMax(Self.significandBitCount - T.significandBitCount))
70 | }
71 | else {
72 | pattern.sig = sig >> UIntMax(bitPattern: IntMax(T.significandBitCount - Self.significandBitCount))
73 | }
74 | }
75 |
76 | self.init(
77 | sign: value.sign,
78 | exponentBitPattern: RawExponent(pattern.exp),
79 | significandBitPattern: RawSignificand(pattern.sig)
80 | )
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/Example/Pods/Anchorage/Source/Priority.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Priority.swift
3 | // Anchorage
4 | //
5 | // Created by Rob Visentin on 5/1/17.
6 | //
7 | // Copyright 2016 Raizlabs and other contributors
8 | // http://raizlabs.com/
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining
11 | // a copy of this software and associated documentation files (the
12 | // Software"), to deal in the Software without restriction, including
13 | // without limitation the rights to use, copy, modify, merge, publish,
14 | // distribute, sublicense, and/or sell copies of the Software, and to
15 | // permit persons to whom the Software is furnished to do so, subject to
16 | // the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be
19 | // included in all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 |
29 | #if os(macOS)
30 | import Cocoa
31 | #else
32 | import UIKit
33 | #endif
34 |
35 | public enum Priority: ExpressibleByFloatLiteral, ExpressibleByIntegerLiteral, Equatable {
36 |
37 | case required
38 | case high
39 | case low
40 | case fittingSize
41 | case custom(LayoutPriority)
42 |
43 | public var value: LayoutPriority {
44 | switch self {
45 | case .required: return LayoutPriorityRequired
46 | case .high: return LayoutPriorityHigh
47 | case .low: return LayoutPriorityLow
48 | case .fittingSize: return LayoutPriorityFittingSize
49 | case .custom(let priority): return priority
50 | }
51 | }
52 |
53 | public init(floatLiteral value: LayoutPriority) {
54 | self.init(value)
55 | }
56 |
57 | public init(integerLiteral value: Int) {
58 | self.init(value)
59 | }
60 |
61 | public init(_ value: Int) {
62 | self = .custom(LayoutPriority(value))
63 | }
64 |
65 | public init(_ value: T) {
66 | self = .custom(LayoutPriority(value))
67 | }
68 |
69 | }
70 |
71 | public func == (lhs: Priority, rhs: Priority) -> Bool {
72 | return lhs.value == rhs.value
73 | }
74 |
75 | public func + (lhs: Priority, rhs: T) -> Priority {
76 | return .custom(lhs.value + LayoutPriority(rhs))
77 | }
78 |
79 | public func + (lhs: T, rhs: Priority) -> Priority {
80 | return .custom(LayoutPriority(lhs) + rhs.value)
81 | }
82 |
83 | public func - (lhs: Priority, rhs: T) -> Priority {
84 | return .custom(lhs.value - LayoutPriority(rhs))
85 | }
86 |
87 | public func - (lhs: T, rhs: Priority) -> Priority {
88 | return .custom(LayoutPriority(lhs) - rhs.value)
89 | }
90 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/FrictionLess.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "FrictionLess",
3 | "version": "0.1.0",
4 | "summary": "A collection of UX-focused swift components for reducing friction in \"user work\".",
5 | "description": "Reduce friction with auto-formatting data entry, auto-advancing forms, and proactive user feedback for valid/invalid input.",
6 | "homepage": "https://github.com/Raizlabs/FrictionLess",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "authors": {
12 | "Jay Clark": "jason.clark@raizlabs.com"
13 | },
14 | "source": {
15 | "git": "https://github.com/raizlabs/FrictionLess.git",
16 | "tag": "0.1.0"
17 | },
18 | "platforms": {
19 | "ios": "10.0"
20 | },
21 | "default_subspecs": "All",
22 | "subspecs": [
23 | {
24 | "name": "FormattableTextField",
25 | "source_files": "FrictionLess/FormattableTextField/**/*",
26 | "frameworks": [
27 | "UIKit"
28 | ]
29 | },
30 | {
31 | "name": "CardEntry",
32 | "source_files": "FrictionLess/CardEntry/**/*.{swift,strings}",
33 | "dependencies": {
34 | "FrictionLess/FormUI": [
35 |
36 | ]
37 | },
38 | "resources": "FrictionLess/CardEntry/CardEntry.xcassets"
39 | },
40 | {
41 | "name": "PhoneFormatter",
42 | "source_files": "FrictionLess/PhoneFormatter/**/*",
43 | "dependencies": {
44 | "FrictionLess/FormattableTextField": [
45 |
46 | ],
47 | "PhoneNumberKit": [
48 |
49 | ]
50 | }
51 | },
52 | {
53 | "name": "FormUI",
54 | "source_files": "FrictionLess/FormUI/**/*.{swift,strings}",
55 | "dependencies": {
56 | "Anchorage": [
57 |
58 | ],
59 | "FrictionLess/FormattableTextField": [
60 |
61 | ]
62 | }
63 | },
64 | {
65 | "name": "All",
66 | "dependencies": {
67 | "FrictionLess/FormattableTextField": [
68 |
69 | ],
70 | "FrictionLess/CardEntry": [
71 |
72 | ],
73 | "FrictionLess/PhoneFormatter": [
74 |
75 | ],
76 | "FrictionLess/FormUI": [
77 |
78 | ]
79 | }
80 | }
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Anchorage (4.0.0)
3 | - FrictionLess (0.1.0):
4 | - FrictionLess/All (= 0.1.0)
5 | - FrictionLess/All (0.1.0):
6 | - FrictionLess/CardEntry
7 | - FrictionLess/FormattableTextField
8 | - FrictionLess/FormUI
9 | - FrictionLess/PhoneFormatter
10 | - FrictionLess/CardEntry (0.1.0):
11 | - FrictionLess/FormUI
12 | - FrictionLess/FormattableTextField (0.1.0)
13 | - FrictionLess/FormUI (0.1.0):
14 | - Anchorage
15 | - FrictionLess/FormattableTextField
16 | - FrictionLess/PhoneFormatter (0.1.0):
17 | - FrictionLess/FormattableTextField
18 | - PhoneNumberKit
19 | - PhoneNumberKit (1.3.0):
20 | - PhoneNumberKit/PhoneNumberKitCore (= 1.3.0)
21 | - PhoneNumberKit/UIKit (= 1.3.0)
22 | - PhoneNumberKit/PhoneNumberKitCore (1.3.0)
23 | - PhoneNumberKit/UIKit (1.3.0):
24 | - PhoneNumberKit/PhoneNumberKitCore
25 |
26 | DEPENDENCIES:
27 | - Anchorage
28 | - FrictionLess (from `../`)
29 |
30 | EXTERNAL SOURCES:
31 | FrictionLess:
32 | :path: ../
33 |
34 | SPEC CHECKSUMS:
35 | Anchorage: fe50dca4e20846b8cb15c859d26fec5132b41577
36 | FrictionLess: 67e8ba3405cf5dfda91070f391cbcace578be7b6
37 | PhoneNumberKit: 08aaf1968ea41a114327e9223cbb0391d76ae145
38 |
39 | PODFILE CHECKSUM: f208593ae0b01d05e3b9f88ac99038748963b697
40 |
41 | COCOAPODS: 1.2.1
42 |
--------------------------------------------------------------------------------
/Example/Pods/PhoneNumberKit/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Roy Marmelstein
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 |
23 |
--------------------------------------------------------------------------------
/Example/Pods/PhoneNumberKit/PhoneNumberKit/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // PhoneNumberKit
4 | //
5 | // Created by Roy Marmelstein on 25/10/2015.
6 | // Copyright © 2015 Roy Marmelstein. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: Private Enums
12 | enum PhoneNumberCountryCodeSource {
13 | case numberWithPlusSign
14 | case numberWithIDD
15 | case numberWithoutPlusSign
16 | case defaultCountry
17 | }
18 |
19 | // MARK: Public Enums
20 |
21 | /**
22 | Enumeration for parsing error types
23 |
24 | - GeneralError: A general error occured.
25 | - InvalidCountryCode: A country code could not be found or the one found was invalid
26 | - NotANumber: The string provided is not a number
27 | - TooLong: The string provided is too long to be a valid number
28 | - TooShort: The string provided is too short to be a valid number
29 | - Deprecated: The method used was deprecated
30 | */
31 | public enum PhoneNumberError: Error {
32 | case generalError
33 | case invalidCountryCode
34 | case notANumber
35 | case unknownType
36 | case tooLong
37 | case tooShort
38 | case deprecated
39 | }
40 |
41 | extension PhoneNumberError: LocalizedError {
42 |
43 | public var errorDescription: String? {
44 | switch self {
45 | case .generalError: return NSLocalizedString("An error occured whilst validating the phone number.", comment: "")
46 | case .invalidCountryCode: return NSLocalizedString("The country code is invalid.", comment: "")
47 | case .notANumber: return NSLocalizedString("The number provided is invalid.", comment: "")
48 | case .unknownType: return NSLocalizedString("Phone number type is unknown.", comment: "")
49 | case .tooLong: return NSLocalizedString("The number provided is too long.", comment: "")
50 | case .tooShort: return NSLocalizedString("The number provided is too short.", comment: "")
51 | case .deprecated: return NSLocalizedString("This function is deprecated.", comment: "")
52 | }
53 | }
54 |
55 | }
56 |
57 | public enum PhoneNumberFormat {
58 | case e164 // +33689123456
59 | case international // +33 6 89 12 34 56
60 | case national // 06 89 12 34 56
61 | }
62 |
63 |
64 | /**
65 | Phone number type enumeration
66 | - fixedLine: Fixed line numbers
67 | - mobile: Mobile numbers
68 | - fixedOrMobile: Either fixed or mobile numbers if we can't tell conclusively.
69 | - pager: Pager numbers
70 | - personalNumber: Personal number numbers
71 | - premiumRate: Premium rate numbers
72 | - sharedCost: Shared cost numbers
73 | - tollFree: Toll free numbers
74 | - voicemail: Voice mail numbers
75 | - vOIP: Voip numbers
76 | - uan: UAN numbers
77 | - unknown: Unknown number type
78 | */
79 | public enum PhoneNumberType {
80 | case fixedLine
81 | case mobile
82 | case fixedOrMobile
83 | case pager
84 | case personalNumber
85 | case premiumRate
86 | case sharedCost
87 | case tollFree
88 | case voicemail
89 | case voip
90 | case uan
91 | case unknown
92 | }
93 |
94 | // MARK: Constants
95 |
96 | struct PhoneNumberConstants {
97 | static let defaultCountry = "US"
98 | static let defaultExtnPrefix = " ext. "
99 | static let longPhoneNumber = "999999999999999"
100 | static let minLengthForNSN = 2
101 | static let maxInputStringLength = 250
102 | static let maxLengthCountryCode = 3
103 | static let maxLengthForNSN = 16
104 | static let nonBreakingSpace = "\u{00a0}"
105 | static let plusChars = "++"
106 | static let validDigitsString = "0-90-9٠-٩۰-۹"
107 | static let digitPlaceholder = "\u{2008}"
108 | static let separatorBeforeNationalNumber = " "
109 | }
110 |
111 | struct PhoneNumberPatterns {
112 | // MARK: Patterns
113 |
114 | static let firstGroupPattern = "(\\$\\d)"
115 | static let fgPattern = "\\$FG"
116 | static let npPattern = "\\$NP"
117 |
118 | static let allNormalizationMappings = ["0":"0", "1":"1", "2":"2", "3":"3", "4":"4", "5":"5", "6":"6", "7":"7", "8":"8", "9":"9"]
119 |
120 | static let capturingDigitPattern = "([0-90-9٠-٩۰-۹])"
121 |
122 | static let extnPattern = "(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xxX##~~;]|int|anexo|int)[:\\..]?[ \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)$"
123 |
124 | static let iddPattern = "^(?:\\+|%@)"
125 |
126 | static let formatPattern = "^(?:%@)$"
127 |
128 | static let characterClassPattern = "\\[([^\\[\\]])*\\]"
129 |
130 | static let standaloneDigitPattern = "\\d(?=[^,}][^,}])"
131 |
132 | static let nationalPrefixParsingPattern = "^(?:%@)"
133 |
134 | static let prefixSeparatorPattern = "[- ]"
135 |
136 | static let eligibleAsYouTypePattern = "^[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~]*(\\$\\d[-x‐-―−ー--/ ()()[].\\[\\]/~⁓∼~]*)+$"
137 |
138 | static let leadingPlusCharsPattern = "^[++]+"
139 |
140 | static let secondNumberStartPattern = "[\\\\\\/] *x"
141 |
142 | static let unwantedEndPattern = "[^0-90-9٠-٩۰-۹A-Za-z#]+$"
143 |
144 | static let validStartPattern = "[++0-90-9٠-٩۰-۹]"
145 |
146 | static let validPhoneNumberPattern = "^[0-90-9٠-٩۰-۹]{2}$|^[++]*(?:[-x\u{2010}-\u{2015}\u{2212}\u{30FC}\u{FF0D}-\u{FF0F} \u{00A0}\u{00AD}\u{200B}\u{2060}\u{3000}()\u{FF08}\u{FF09}\u{FF3B}\u{FF3D}.\\[\\]/~\u{2053}\u{223C}\u{FF5E}*]*[0-9\u{FF10}-\u{FF19}\u{0660}-\u{0669}\u{06F0}-\u{06F9}]){3,}[-x\u{2010}-\u{2015}\u{2212}\u{30FC}\u{FF0D}-\u{FF0F} \u{00A0}\u{00AD}\u{200B}\u{2060}\u{3000}()\u{FF08}\u{FF09}\u{FF3B}\u{FF3D}.\\[\\]/~\u{2053}\u{223C}\u{FF5E}*A-Za-z0-9\u{FF10}-\u{FF19}\u{0660}-\u{0669}\u{06F0}-\u{06F9}]*(?:(?:;ext=([0-90-9٠-٩۰-۹]{1,7})|[ \\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|e?xtn?|[,xxX##~~;]|int|anexo|int)[:\\..]?[ \\t,-]*([0-90-9٠-٩۰-۹]{1,7})#?|[- ]+([0-90-9٠-٩۰-۹]{1,5})#)?$)?$"
147 | }
148 |
--------------------------------------------------------------------------------
/Example/Pods/PhoneNumberKit/PhoneNumberKit/Formatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Formatter.swift
3 | // PhoneNumberKit
4 | //
5 | // Created by Roy Marmelstein on 03/11/2015.
6 | // Copyright © 2015 Roy Marmelstein. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class Formatter {
12 |
13 | weak var regexManager: RegexManager?
14 |
15 | init(phoneNumberKit: PhoneNumberKit) {
16 | self.regexManager = phoneNumberKit.regexManager
17 | }
18 |
19 | init(regexManager: RegexManager) {
20 | self.regexManager = regexManager
21 | }
22 |
23 |
24 | // MARK: Formatting functions
25 |
26 | /// Formats phone numbers for display
27 | ///
28 | /// - Parameters:
29 | /// - phoneNumber: Phone number object.
30 | /// - formatType: Format type.
31 | /// - regionMetadata: Region meta data.
32 | /// - Returns: Formatted Modified national number ready for display.
33 | func format(phoneNumber: PhoneNumber, formatType: PhoneNumberFormat, regionMetadata: MetadataTerritory?) -> String {
34 | var formattedNationalNumber = phoneNumber.adjustedNationalNumber()
35 | if let regionMetadata = regionMetadata {
36 | formattedNationalNumber = formatNationalNumber(formattedNationalNumber, regionMetadata: regionMetadata, formatType: formatType)
37 | if let formattedExtension = formatExtension(phoneNumber.numberExtension, regionMetadata: regionMetadata) {
38 | formattedNationalNumber = formattedNationalNumber + formattedExtension
39 | }
40 | }
41 | return formattedNationalNumber
42 | }
43 |
44 | /// Formats extension for display
45 | ///
46 | /// - Parameters:
47 | /// - numberExtension: Number extension string.
48 | /// - regionMetadata: Region meta data.
49 | /// - Returns: Modified number extension with either a preferred extension prefix or the default one.
50 | func formatExtension(_ numberExtension: String?, regionMetadata: MetadataTerritory) -> String? {
51 | if let extns = numberExtension {
52 | if let preferredExtnPrefix = regionMetadata.preferredExtnPrefix {
53 | return "\(preferredExtnPrefix)\(extns)"
54 | }
55 | else {
56 | return "\(PhoneNumberConstants.defaultExtnPrefix)\(extns)"
57 | }
58 | }
59 | return nil
60 | }
61 |
62 | /// Formats national number for display
63 | ///
64 | /// - Parameters:
65 | /// - nationalNumber: National number string.
66 | /// - regionMetadata: Region meta data.
67 | /// - formatType: Format type.
68 | /// - Returns: Modified nationalNumber for display.
69 | func formatNationalNumber(_ nationalNumber: String, regionMetadata: MetadataTerritory, formatType: PhoneNumberFormat) -> String {
70 | guard let regexManager = regexManager else { return nationalNumber }
71 | let formats = regionMetadata.numberFormats
72 | var selectedFormat: MetadataPhoneNumberFormat?
73 | for format in formats {
74 | if let leadingDigitPattern = format.leadingDigitsPatterns?.last {
75 | if (regexManager.stringPositionByRegex(leadingDigitPattern, string: String(nationalNumber)) == 0) {
76 | if (regexManager.matchesEntirely(format.pattern, string: String(nationalNumber))) {
77 | selectedFormat = format
78 | break;
79 | }
80 | }
81 | }
82 | else {
83 | if (regexManager.matchesEntirely(format.pattern, string: String(nationalNumber))) {
84 | selectedFormat = format
85 | break;
86 | }
87 | }
88 | }
89 | if let formatPattern = selectedFormat {
90 | guard let numberFormatRule = (formatType == PhoneNumberFormat.international && formatPattern.intlFormat != nil) ? formatPattern.intlFormat : formatPattern.format, let pattern = formatPattern.pattern else {
91 | return nationalNumber
92 | }
93 | var formattedNationalNumber = String()
94 | var prefixFormattingRule = String()
95 | if let nationalPrefixFormattingRule = formatPattern.nationalPrefixFormattingRule, let nationalPrefix = regionMetadata.nationalPrefix {
96 | prefixFormattingRule = regexManager.replaceStringByRegex(PhoneNumberPatterns.npPattern, string: nationalPrefixFormattingRule, template: nationalPrefix)
97 | prefixFormattingRule = regexManager.replaceStringByRegex(PhoneNumberPatterns.fgPattern, string: prefixFormattingRule, template:"\\$1")
98 | }
99 | if formatType == PhoneNumberFormat.national && regexManager.hasValue(prefixFormattingRule){
100 | let replacePattern = regexManager.replaceFirstStringByRegex(PhoneNumberPatterns.firstGroupPattern, string: numberFormatRule, templateString: prefixFormattingRule)
101 | formattedNationalNumber = regexManager.replaceStringByRegex(pattern, string: nationalNumber, template: replacePattern)
102 | }
103 | else {
104 | formattedNationalNumber = regexManager.replaceStringByRegex(pattern, string: nationalNumber, template: numberFormatRule)
105 | }
106 | return formattedNationalNumber
107 | }
108 | else {
109 | return nationalNumber
110 | }
111 | }
112 |
113 | }
114 |
115 | public extension PhoneNumber {
116 |
117 | /**
118 | Adjust national number for display by adding leading zero if needed. Used for basic formatting functions.
119 | - Returns: A string representing the adjusted national number.
120 | */
121 | public func adjustedNationalNumber() -> String {
122 | if self.leadingZero == true {
123 | return "0" + String(nationalNumber)
124 | }
125 | else {
126 | return String(nationalNumber)
127 | }
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/Example/Pods/PhoneNumberKit/PhoneNumberKit/MetadataManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Metadata.swift
3 | // PhoneNumberKit
4 | //
5 | // Created by Roy Marmelstein on 03/10/2015.
6 | // Copyright © 2015 Roy Marmelstein. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class MetadataManager {
12 |
13 | var territories = [MetadataTerritory]()
14 | var territoriesByCode = [UInt64: [MetadataTerritory]]()
15 | var mainTerritoryByCode = [UInt64: MetadataTerritory]()
16 | var territoriesByCountry = [String: MetadataTerritory]()
17 |
18 | // MARK: Lifecycle
19 |
20 | /**
21 | Private init populates metadata territories and the two hashed dictionaries for faster lookup.
22 | */
23 | public init () {
24 | territories = populateTerritories()
25 | for item in territories {
26 | var currentTerritories: [MetadataTerritory] = territoriesByCode[item.countryCode] ?? [MetadataTerritory]()
27 | currentTerritories.append(item)
28 | territoriesByCode[item.countryCode] = currentTerritories
29 | if mainTerritoryByCode[item.countryCode] == nil || item.mainCountryForCode == true {
30 | mainTerritoryByCode[item.countryCode] = item
31 | }
32 | territoriesByCountry[item.codeID] = item
33 | }
34 | }
35 |
36 | deinit {
37 | territories.removeAll()
38 | territoriesByCode.removeAll()
39 | territoriesByCountry.removeAll()
40 | }
41 |
42 |
43 | /// Populates the metadata from the included json file resource.
44 | ///
45 | /// - returns: array of MetadataTerritory objects
46 | fileprivate func populateTerritories() -> [MetadataTerritory] {
47 | var territoryArray = [MetadataTerritory]()
48 | let frameworkBundle = Bundle(for: PhoneNumberKit.self)
49 | do {
50 | if let jsonPath = frameworkBundle.path(forResource: "PhoneNumberMetadata", ofType: "json"), let jsonData = try? Data(contentsOf: URL(fileURLWithPath: jsonPath)), let jsonObjects = try JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments) as? NSDictionary, let metadataDict = jsonObjects["phoneNumberMetadata"] as? NSDictionary, let metadataTerritories = metadataDict["territories"] as? NSDictionary , let metadataTerritoryArray = metadataTerritories["territory"] as? NSArray {
51 | metadataTerritoryArray.forEach({
52 | if let territoryDict = $0 as? NSDictionary {
53 | let parsedTerritory = MetadataTerritory(jsondDict: territoryDict)
54 | territoryArray.append(parsedTerritory)
55 | }
56 | })
57 | }
58 | }
59 | catch {}
60 | return territoryArray
61 | }
62 |
63 | // MARK: Filters
64 |
65 | /// Get an array of MetadataTerritory objects corresponding to a given country code.
66 | ///
67 | /// - parameter code: international country code (e.g 44 for the UK).
68 | ///
69 | /// - returns: optional array of MetadataTerritory objects.
70 | internal func filterTerritories(byCode code: UInt64) -> [MetadataTerritory]? {
71 | return territoriesByCode[code]
72 | }
73 |
74 | /// Get the MetadataTerritory objects for an ISO 639 compliant region code.
75 | ///
76 | /// - parameter country: ISO 639 compliant region code (e.g "GB" for the UK).
77 | ///
78 | /// - returns: A MetadataTerritory object.
79 | internal func filterTerritories(byCountry country: String) -> MetadataTerritory? {
80 | return territoriesByCountry[country.uppercased()]
81 | }
82 |
83 | /// Get the main MetadataTerritory objects for a given country code.
84 | ///
85 | /// - parameter code: An international country code (e.g 1 for the US).
86 | ///
87 | /// - returns: A MetadataTerritory object.
88 | internal func mainTerritory(forCode code: UInt64) -> MetadataTerritory? {
89 | return mainTerritoryByCode[code]
90 | }
91 |
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/Example/Pods/PhoneNumberKit/PhoneNumberKit/NSRegularExpression+Swift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSRegularExpression+Swift.swift
3 | // PhoneNumberKit
4 | //
5 | // Created by David Beck on 8/15/16.
6 | // Copyright © 2016 Roy Marmelstein. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | func nsRange(from range: Range) -> NSRange {
13 | let utf16view = self.utf16
14 | let from = range.lowerBound.samePosition(in: utf16view)
15 | let to = range.upperBound.samePosition(in: utf16view)
16 | return NSMakeRange(utf16view.distance(from: utf16view.startIndex, to: from),
17 | utf16view.distance(from: from, to: to))
18 | }
19 |
20 | func range(from nsRange: NSRange) -> Range? {
21 | guard
22 | let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
23 | let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
24 | let from = String.Index(from16, within: self),
25 | let to = String.Index(to16, within: self)
26 | else { return nil }
27 | return from ..< to
28 | }
29 | }
30 |
31 |
32 | extension NSRegularExpression {
33 | func enumerateMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: Range? = nil, using block: (NSTextCheckingResult?, NSRegularExpression.MatchingFlags, UnsafeMutablePointer) -> Swift.Void) {
34 | let range = range ?? string.startIndex..? = nil) -> [NSTextCheckingResult] {
41 | let range = range ?? string.startIndex..? = nil) -> Int {
48 | let range = range ?? string.startIndex..? = nil) -> NSTextCheckingResult? {
55 | let range = range ?? string.startIndex..? = nil) -> Range? {
62 | let range = range ?? string.startIndex..? = nil, withTemplate templ: String) -> String {
71 | let range = range ?? string.startIndex.. Bool {
33 | return (lhs.countryCode == rhs.countryCode)
34 | && (lhs.leadingZero == rhs.leadingZero)
35 | && (lhs.nationalNumber == rhs.nationalNumber)
36 | && (lhs.numberExtension == rhs.numberExtension)
37 | }
38 |
39 | }
40 |
41 | extension PhoneNumber : Hashable {
42 |
43 | public var hashValue: Int {
44 | return countryCode.hashValue ^ nationalNumber.hashValue ^ leadingZero.hashValue ^ (numberExtension?.hashValue ?? 0)
45 | }
46 |
47 | }
48 |
49 | /// In past versions of PhoneNumebrKit you were able to initialize a PhoneNumber object to parse a String. Please use a PhoneNumberKit object's methods.
50 | public extension PhoneNumber {
51 | /**
52 | DEPRECATED.
53 | Parse a string into a phone number object using default region. Can throw.
54 | - Parameter rawNumber: String to be parsed to phone number struct.
55 | */
56 | @available(*, unavailable, message: "use PhoneNumberKit instead to produce PhoneNumbers")
57 | public init(rawNumber: String) throws {
58 | assertionFailure(PhoneNumberError.deprecated.localizedDescription)
59 | throw PhoneNumberError.deprecated
60 | }
61 |
62 | /**
63 | DEPRECATED.
64 | Parse a string into a phone number object using custom region. Can throw.
65 | - Parameter rawNumber: String to be parsed to phone number struct.
66 | - Parameter region: ISO 639 compliant region code.
67 | */
68 | @available(*, unavailable, message: "use PhoneNumberKit instead to produce PhoneNumbers")
69 | public init(rawNumber: String, region: String) throws {
70 | throw PhoneNumberError.deprecated
71 | }
72 |
73 | }
74 |
75 |
76 |
--------------------------------------------------------------------------------
/Example/Pods/PhoneNumberKit/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](http://cocoapods.org/?q=PhoneNumberKit)
3 | [](https://travis-ci.org/marmelroy/PhoneNumberKit) [](http://cocoapods.org/?q=PhoneNumberKit)
4 | [](https://github.com/Carthage/Carthage)
5 |
6 | # PhoneNumberKit
7 | Swift 3.0 framework for parsing, formatting and validating international phone numbers.
8 | Inspired by Google's libphonenumber.
9 |
10 | [Migrating from PhoneNumberKit 0.x? See the migration guide.](https://github.com/marmelroy/PhoneNumberKit/blob/master/Documentation/OXMIGRATIONGUIDE.md)
11 | ## Features
12 |
13 | | |Features |
14 | --------------------------|------------------------------------------------------------
15 | :phone: | Validate, normalize and extract the elements of any phone number string.
16 | :100: | Simple Swift syntax and a lightweight readable codebase.
17 | :checkered_flag: | Fast. 1000 parses -> ~0.4 seconds.
18 | :books: | Best-in-class metadata from Google's libPhoneNumber project.
19 | :trophy: | Fully tested to match the accuracy of Google's JavaScript implementation of libPhoneNumber.
20 | :iphone: | Built for iOS. Automatically grabs the default region code from the phone.
21 | 📝 | Editable (!) AsYouType formatter for UITextField.
22 | :us: | Convert country codes to country names and vice versa
23 |
24 | ## Usage
25 |
26 | Import PhoneNumberKit at the top of the Swift file that will interact with a phone number.
27 |
28 | ```swift
29 | import PhoneNumberKit
30 | ```
31 |
32 | All of your interactions with PhoneNumberKit happen through a PhoneNumberKit object. The first step you should take is to allocate one.
33 |
34 | A PhoneNumberKit instance is relatively expensive to allocate (it parses the metadata and keeps it in memory for the object's lifecycle), you should try and make sure PhoneNumberKit is allocated once and deallocated when no longer needed.
35 |
36 | ```swift
37 | let phoneNumberKit = PhoneNumberKit()
38 | ```
39 |
40 | To parse a string, use the parse function. The region code is automatically computed but can be overridden if needed. PhoneNumberKit automatically does a hard type validation to ensure that the object created is valid, this can be quite costly performance-wise and can be turned off if needed.
41 | ```swift
42 | do {
43 | let phoneNumber = try phoneNumberKit.parse("+33 6 89 017383")
44 | let phoneNumberCustomDefaultRegion = try phoneNumberKit.parse("+44 20 7031 3000", withRegion: "GB", ignoreType: true)
45 | }
46 | catch {
47 | print("Generic parser error")
48 | }
49 | ```
50 |
51 | If you need to parse and validate a large amount of numbers at once, PhoneNumberKit has a special, lightning fast array parsing function. The default region code is automatically computed but can be overridden if needed. Here you can also ignore hard type validation if it is not necessary. Invalid numbers are ignored in the resulting array.
52 | ```swift
53 | let rawNumberArray = ["0291 12345678", "+49 291 12345678", "04134 1234", "09123 12345"]
54 | let phoneNumbers = phoneNumberKit.parse(rawNumberArray)
55 | let phoneNumbersCustomDefaultRegion = phoneNumberKit.parse(rawNumberArray, withRegion: "DE", ignoreType: true)
56 | ```
57 |
58 | PhoneNumber objects are immutable Swift structs with the following properties:
59 | ```swift
60 | phoneNumber.numberString
61 | phoneNumber.countryCode
62 | phoneNumber.nationalNumber
63 | phoneNumber.numberExtension
64 | phoneNumber.type // e.g Mobile or Fixed
65 | ```
66 |
67 | Formatting a PhoneNumber object into a string is also very easy
68 | ```swift
69 | phoneNumberKit.format(phoneNumber, toType: .e164) // +61236618300
70 | phoneNumberKit.format(phoneNumber, toType: .international) // +61 2 3661 8300
71 | phoneNumberKit.format(phoneNumber, toType: .national) // (02) 3661 8300
72 | ```
73 |
74 | To use the AsYouTypeFormatter, just replace your UITextField with a PhoneNumberTextField (if you are using Interface Builder make sure the module field is set to PhoneNumberKit).
75 |
76 | PhoneNumberTextField automatically formats phone numbers and gives the user full editing capabilities. If you want to customize you can use the PartialFormatter directly. The default region code is automatically computed but can be overridden if needed.
77 |
78 | 
79 |
80 | ```swift
81 | let textField = PhoneNumberTextField()
82 |
83 | PartialFormatter().formatPartial("+336895555") // +33 6 89 55 55
84 | ```
85 |
86 | You can also query countries for a dialing code or the dialing code for a given country
87 | ```swift
88 | phoneNumberKit.countries(withCode: 33)
89 | phoneNumberKit.countryCode(for: "FR")
90 | ```
91 |
92 | ### Setting up with Carthage
93 |
94 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.
95 |
96 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
97 |
98 | ```bash
99 | $ brew update
100 | $ brew install carthage
101 | ```
102 |
103 | To integrate PhoneNumberKit into your Xcode project using Carthage, specify it in your `Cartfile`:
104 |
105 | ```ogdl
106 | github "marmelroy/PhoneNumberKit"
107 | ```
108 |
109 | ### Setting up with [CocoaPods](http://cocoapods.org/?q=PhoneNumberKit)
110 | ```ruby
111 | source 'https://github.com/CocoaPods/Specs.git'
112 | pod 'PhoneNumberKit', '~> 1.3'
113 | ```
114 |
--------------------------------------------------------------------------------
/Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/FrictionLess.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
66 |
67 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Anchorage/Anchorage-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Anchorage : NSObject
3 | @end
4 | @implementation PodsDummy_Anchorage
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Anchorage/Anchorage-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Anchorage/Anchorage-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double AnchorageVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char AnchorageVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Anchorage/Anchorage.modulemap:
--------------------------------------------------------------------------------
1 | framework module Anchorage {
2 | umbrella header "Anchorage-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Anchorage/Anchorage.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Anchorage
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
5 | PODS_BUILD_DIR = $BUILD_DIR
6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Anchorage
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Anchorage/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 4.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/FrictionLess/FrictionLess-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_FrictionLess : NSObject
3 | @end
4 | @implementation PodsDummy_FrictionLess
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/FrictionLess/FrictionLess-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/FrictionLess/FrictionLess-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double FrictionLessVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char FrictionLessVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/FrictionLess/FrictionLess.modulemap:
--------------------------------------------------------------------------------
1 | framework module FrictionLess {
2 | umbrella header "FrictionLess-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/FrictionLess/FrictionLess.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/FrictionLess
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Anchorage" "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
5 | OTHER_LDFLAGS = -framework "UIKit"
6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
7 | PODS_BUILD_DIR = $BUILD_DIR
8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_ROOT = ${SRCROOT}
10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
12 | SKIP_INSTALL = YES
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/FrictionLess/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 0.1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/PhoneNumberKit/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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.3.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/PhoneNumberKit/PhoneNumberKit-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_PhoneNumberKit : NSObject
3 | @end
4 | @implementation PodsDummy_PhoneNumberKit
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/PhoneNumberKit/PhoneNumberKit-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/PhoneNumberKit/PhoneNumberKit-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double PhoneNumberKitVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char PhoneNumberKitVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/PhoneNumberKit/PhoneNumberKit.modulemap:
--------------------------------------------------------------------------------
1 | framework module PhoneNumberKit {
2 | umbrella header "PhoneNumberKit-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/PhoneNumberKit/PhoneNumberKit.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_LDFLAGS = -framework "CoreTelephony"
5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
6 | PODS_BUILD_DIR = $BUILD_DIR
7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_ROOT = ${SRCROOT}
9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/PhoneNumberKit
10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
11 | SKIP_INSTALL = YES
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Anchorage
5 |
6 | The MIT License (MIT)
7 |
8 | Copyright (c) 2016 Raizlabs
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 |
29 | ## FrictionLess
30 |
31 | Copyright (c) 2017 jason.clark@raizlabs.com
32 |
33 | Permission is hereby granted, free of charge, to any person obtaining a copy
34 | of this software and associated documentation files (the "Software"), to deal
35 | in the Software without restriction, including without limitation the rights
36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37 | copies of the Software, and to permit persons to whom the Software is
38 | furnished to do so, subject to the following conditions:
39 |
40 | The above copyright notice and this permission notice shall be included in
41 | all copies or substantial portions of the Software.
42 |
43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
49 | THE SOFTWARE.
50 |
51 |
52 | ## PhoneNumberKit
53 |
54 | The MIT License (MIT)
55 |
56 | Copyright (c) 2015 Roy Marmelstein
57 |
58 | Permission is hereby granted, free of charge, to any person obtaining a copy
59 | of this software and associated documentation files (the "Software"), to deal
60 | in the Software without restriction, including without limitation the rights
61 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
62 | copies of the Software, and to permit persons to whom the Software is
63 | furnished to do so, subject to the following conditions:
64 |
65 | The above copyright notice and this permission notice shall be included in all
66 | copies or substantial portions of the Software.
67 |
68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
69 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
70 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
71 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
72 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
73 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
74 | SOFTWARE.
75 |
76 |
77 | Generated by CocoaPods - https://cocoapods.org
78 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | The MIT License (MIT)
18 |
19 | Copyright (c) 2016 Raizlabs
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.
38 |
39 | License
40 | MIT
41 | Title
42 | Anchorage
43 | Type
44 | PSGroupSpecifier
45 |
46 |
47 | FooterText
48 | Copyright (c) 2017 jason.clark@raizlabs.com <jason.clark@raizlabs.com>
49 |
50 | Permission is hereby granted, free of charge, to any person obtaining a copy
51 | of this software and associated documentation files (the "Software"), to deal
52 | in the Software without restriction, including without limitation the rights
53 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
54 | copies of the Software, and to permit persons to whom the Software is
55 | furnished to do so, subject to the following conditions:
56 |
57 | The above copyright notice and this permission notice shall be included in
58 | all copies or substantial portions of the Software.
59 |
60 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
61 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
62 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
63 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
64 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
65 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
66 | THE SOFTWARE.
67 |
68 | License
69 | MIT
70 | Title
71 | FrictionLess
72 | Type
73 | PSGroupSpecifier
74 |
75 |
76 | FooterText
77 | The MIT License (MIT)
78 |
79 | Copyright (c) 2015 Roy Marmelstein
80 |
81 | Permission is hereby granted, free of charge, to any person obtaining a copy
82 | of this software and associated documentation files (the "Software"), to deal
83 | in the Software without restriction, including without limitation the rights
84 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
85 | copies of the Software, and to permit persons to whom the Software is
86 | furnished to do so, subject to the following conditions:
87 |
88 | The above copyright notice and this permission notice shall be included in all
89 | copies or substantial portions of the Software.
90 |
91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
92 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
93 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
94 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
95 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
96 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
97 | SOFTWARE.
98 |
99 |
100 | License
101 | MIT
102 | Title
103 | PhoneNumberKit
104 | Type
105 | PSGroupSpecifier
106 |
107 |
108 | FooterText
109 | Generated by CocoaPods - https://cocoapods.org
110 | Title
111 |
112 | Type
113 | PSGroupSpecifier
114 |
115 |
116 | StringsTable
117 | Acknowledgements
118 | Title
119 | Acknowledgements
120 |
121 |
122 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_FrictionLess_Example : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_FrictionLess_Example
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | install_framework()
10 | {
11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
12 | local source="${BUILT_PRODUCTS_DIR}/$1"
13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
15 | elif [ -r "$1" ]; then
16 | local source="$1"
17 | fi
18 |
19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
20 |
21 | if [ -L "${source}" ]; then
22 | echo "Symlinked..."
23 | source="$(readlink "${source}")"
24 | fi
25 |
26 | # use filter instead of exclude so missing patterns dont' throw errors
27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
29 |
30 | local basename
31 | basename="$(basename -s .framework "$1")"
32 | binary="${destination}/${basename}.framework/${basename}"
33 | if ! [ -r "$binary" ]; then
34 | binary="${destination}/${basename}"
35 | fi
36 |
37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
39 | strip_invalid_archs "$binary"
40 | fi
41 |
42 | # Resign the code if required by the build settings to avoid unstable apps
43 | code_sign_if_enabled "${destination}/$(basename "$1")"
44 |
45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
47 | local swift_runtime_libs
48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
49 | for lib in $swift_runtime_libs; do
50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
52 | code_sign_if_enabled "${destination}/${lib}"
53 | done
54 | fi
55 | }
56 |
57 | # Signs a framework with the provided identity
58 | code_sign_if_enabled() {
59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
60 | # Use the current code_sign_identitiy
61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
63 |
64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
65 | code_sign_cmd="$code_sign_cmd &"
66 | fi
67 | echo "$code_sign_cmd"
68 | eval "$code_sign_cmd"
69 | fi
70 | }
71 |
72 | # Strip invalid architectures
73 | strip_invalid_archs() {
74 | binary="$1"
75 | # Get architectures for current file
76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
77 | stripped=""
78 | for arch in $archs; do
79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
80 | # Strip non-valid architectures in-place
81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
82 | stripped="$stripped $arch"
83 | fi
84 | done
85 | if [[ "$stripped" ]]; then
86 | echo "Stripped $binary of architectures:$stripped"
87 | fi
88 | }
89 |
90 |
91 | if [[ "$CONFIGURATION" == "Debug" ]]; then
92 | install_framework "$BUILT_PRODUCTS_DIR/Anchorage/Anchorage.framework"
93 | install_framework "$BUILT_PRODUCTS_DIR/FrictionLess/FrictionLess.framework"
94 | install_framework "$BUILT_PRODUCTS_DIR/PhoneNumberKit/PhoneNumberKit.framework"
95 | fi
96 | if [[ "$CONFIGURATION" == "Release" ]]; then
97 | install_framework "$BUILT_PRODUCTS_DIR/Anchorage/Anchorage.framework"
98 | install_framework "$BUILT_PRODUCTS_DIR/FrictionLess/FrictionLess.framework"
99 | install_framework "$BUILT_PRODUCTS_DIR/PhoneNumberKit/PhoneNumberKit.framework"
100 | fi
101 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
102 | wait
103 | fi
104 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | case "${TARGETED_DEVICE_FAMILY}" in
12 | 1,2)
13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
14 | ;;
15 | 1)
16 | TARGET_DEVICE_ARGS="--target-device iphone"
17 | ;;
18 | 2)
19 | TARGET_DEVICE_ARGS="--target-device ipad"
20 | ;;
21 | 3)
22 | TARGET_DEVICE_ARGS="--target-device tv"
23 | ;;
24 | 4)
25 | TARGET_DEVICE_ARGS="--target-device watch"
26 | ;;
27 | *)
28 | TARGET_DEVICE_ARGS="--target-device mac"
29 | ;;
30 | esac
31 |
32 | install_resource()
33 | {
34 | if [[ "$1" = /* ]] ; then
35 | RESOURCE_PATH="$1"
36 | else
37 | RESOURCE_PATH="${PODS_ROOT}/$1"
38 | fi
39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
40 | cat << EOM
41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
42 | EOM
43 | exit 1
44 | fi
45 | case $RESOURCE_PATH in
46 | *.storyboard)
47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
49 | ;;
50 | *.xib)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.framework)
55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
59 | ;;
60 | *.xcdatamodel)
61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
63 | ;;
64 | *.xcdatamodeld)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
67 | ;;
68 | *.xcmappingmodel)
69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
71 | ;;
72 | *.xcassets)
73 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
75 | ;;
76 | *)
77 | echo "$RESOURCE_PATH"
78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
79 | ;;
80 | esac
81 | }
82 |
83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | fi
89 | rm -f "$RESOURCES_TO_COPY"
90 |
91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
92 | then
93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
95 | while read line; do
96 | if [[ $line != "${PODS_ROOT}*" ]]; then
97 | XCASSET_FILES+=("$line")
98 | fi
99 | done <<<"$OTHER_XCASSETS"
100 |
101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
102 | fi
103 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_FrictionLess_ExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_FrictionLess_ExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Anchorage" "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess" "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Anchorage/Anchorage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess/FrictionLess.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit/PhoneNumberKit.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Anchorage" -framework "FrictionLess" -framework "PhoneNumberKit"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = $BUILD_DIR
9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_FrictionLess_Example {
2 | umbrella header "Pods-FrictionLess_Example-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Example/Pods-FrictionLess_Example.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Anchorage" "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess" "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Anchorage/Anchorage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess/FrictionLess.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit/PhoneNumberKit.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Anchorage" -framework "FrictionLess" -framework "PhoneNumberKit"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = $BUILD_DIR
9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_FrictionLess_Tests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_FrictionLess_Tests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | install_framework()
10 | {
11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
12 | local source="${BUILT_PRODUCTS_DIR}/$1"
13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
15 | elif [ -r "$1" ]; then
16 | local source="$1"
17 | fi
18 |
19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
20 |
21 | if [ -L "${source}" ]; then
22 | echo "Symlinked..."
23 | source="$(readlink "${source}")"
24 | fi
25 |
26 | # use filter instead of exclude so missing patterns dont' throw errors
27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
29 |
30 | local basename
31 | basename="$(basename -s .framework "$1")"
32 | binary="${destination}/${basename}.framework/${basename}"
33 | if ! [ -r "$binary" ]; then
34 | binary="${destination}/${basename}"
35 | fi
36 |
37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
39 | strip_invalid_archs "$binary"
40 | fi
41 |
42 | # Resign the code if required by the build settings to avoid unstable apps
43 | code_sign_if_enabled "${destination}/$(basename "$1")"
44 |
45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
47 | local swift_runtime_libs
48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
49 | for lib in $swift_runtime_libs; do
50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
52 | code_sign_if_enabled "${destination}/${lib}"
53 | done
54 | fi
55 | }
56 |
57 | # Signs a framework with the provided identity
58 | code_sign_if_enabled() {
59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
60 | # Use the current code_sign_identitiy
61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
63 |
64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
65 | code_sign_cmd="$code_sign_cmd &"
66 | fi
67 | echo "$code_sign_cmd"
68 | eval "$code_sign_cmd"
69 | fi
70 | }
71 |
72 | # Strip invalid architectures
73 | strip_invalid_archs() {
74 | binary="$1"
75 | # Get architectures for current file
76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
77 | stripped=""
78 | for arch in $archs; do
79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
80 | # Strip non-valid architectures in-place
81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
82 | stripped="$stripped $arch"
83 | fi
84 | done
85 | if [[ "$stripped" ]]; then
86 | echo "Stripped $binary of architectures:$stripped"
87 | fi
88 | }
89 |
90 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
91 | wait
92 | fi
93 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | case "${TARGETED_DEVICE_FAMILY}" in
12 | 1,2)
13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
14 | ;;
15 | 1)
16 | TARGET_DEVICE_ARGS="--target-device iphone"
17 | ;;
18 | 2)
19 | TARGET_DEVICE_ARGS="--target-device ipad"
20 | ;;
21 | 3)
22 | TARGET_DEVICE_ARGS="--target-device tv"
23 | ;;
24 | 4)
25 | TARGET_DEVICE_ARGS="--target-device watch"
26 | ;;
27 | *)
28 | TARGET_DEVICE_ARGS="--target-device mac"
29 | ;;
30 | esac
31 |
32 | install_resource()
33 | {
34 | if [[ "$1" = /* ]] ; then
35 | RESOURCE_PATH="$1"
36 | else
37 | RESOURCE_PATH="${PODS_ROOT}/$1"
38 | fi
39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
40 | cat << EOM
41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
42 | EOM
43 | exit 1
44 | fi
45 | case $RESOURCE_PATH in
46 | *.storyboard)
47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
49 | ;;
50 | *.xib)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.framework)
55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
59 | ;;
60 | *.xcdatamodel)
61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
63 | ;;
64 | *.xcdatamodeld)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
67 | ;;
68 | *.xcmappingmodel)
69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
71 | ;;
72 | *.xcassets)
73 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
75 | ;;
76 | *)
77 | echo "$RESOURCE_PATH"
78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
79 | ;;
80 | esac
81 | }
82 |
83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | fi
89 | rm -f "$RESOURCES_TO_COPY"
90 |
91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
92 | then
93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
95 | while read line; do
96 | if [[ $line != "${PODS_ROOT}*" ]]; then
97 | XCASSET_FILES+=("$line")
98 | fi
99 | done <<<"$OTHER_XCASSETS"
100 |
101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
102 | fi
103 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_FrictionLess_TestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_FrictionLess_TestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Anchorage" "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess" "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Anchorage/Anchorage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess/FrictionLess.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit/PhoneNumberKit.framework/Headers"
5 | PODS_BUILD_DIR = $BUILD_DIR
6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
8 | PODS_ROOT = ${SRCROOT}/Pods
9 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_FrictionLess_Tests {
2 | umbrella header "Pods-FrictionLess_Tests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-FrictionLess_Tests/Pods-FrictionLess_Tests.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Anchorage" "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess" "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Anchorage/Anchorage.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/FrictionLess/FrictionLess.framework/Headers" -iquote "$PODS_CONFIGURATION_BUILD_DIR/PhoneNumberKit/PhoneNumberKit.framework/Headers"
5 | PODS_BUILD_DIR = $BUILD_DIR
6 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
8 | PODS_ROOT = ${SRCROOT}/Pods
9 |
--------------------------------------------------------------------------------
/Example/Tests/CardStateTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardStateTests.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/21/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import FrictionLess
11 |
12 | class CardStateTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testEmptyStringIndeterminateState() {
25 | if case .indeterminate(let cards) = CardState(fromPrefix: ""),
26 | cards == CardType.allValues {
27 | XCTAssert(true)
28 | }
29 | else {
30 | XCTFail("Empty string should return all card types as potential matches")
31 | }
32 | }
33 |
34 | func testIndeterminate() {
35 | if case .indeterminate(let cards) = CardState(fromPrefix: "3"),
36 | cards.contains(.amex),
37 | cards.contains(.diners),
38 | cards.contains(.jcb) {
39 | XCTAssert(true)
40 | }
41 | else {
42 | XCTFail()
43 | }
44 | }
45 |
46 | func testCards() {
47 | //amex
48 | let amex = ["378282246310005",
49 | "371449635398431",
50 | "378734493671000"]
51 | amex.forEach {
52 | XCTAssert(CardState(fromPrefix: $0) == .identified(.amex))
53 | XCTAssert(CardState(fromNumber: $0) == .identified(.amex))
54 | }
55 |
56 | //diners
57 | let diners = ["30569309025904",
58 | "38520000023237"]
59 | diners.forEach {
60 | XCTAssert(CardState(fromPrefix: $0) == .identified(.diners))
61 | XCTAssert(CardState(fromNumber: $0) == .identified(.diners))
62 | }
63 |
64 | //discover
65 | let discover = ["6011111111111117",
66 | "6011000990139424",
67 | "6510000000000000"]
68 | discover.forEach {
69 | XCTAssert(CardState(fromPrefix: $0) == .identified(.discover))
70 | XCTAssert(CardState(fromNumber: $0) == .identified(.discover))
71 | }
72 |
73 | //jcb
74 | let jcb = ["3530111333300000",
75 | "3566002020360505"]
76 | jcb.forEach {
77 | XCTAssert(CardState(fromPrefix: $0) == .identified(.jcb))
78 | XCTAssert(CardState(fromNumber: $0) == .identified(.jcb))
79 | }
80 |
81 | //mastercard
82 | let mastercard = ["5555555555554444",
83 | "5105105105105100",
84 | "5454545454545454",
85 | "2221900000000000"]
86 | mastercard.forEach {
87 | XCTAssert(CardState(fromPrefix: $0) == .identified(.masterCard))
88 | XCTAssert(CardState(fromNumber: $0) == .identified(.masterCard))
89 | }
90 |
91 | //visa
92 | let visa = ["4444333322221111",
93 | "4111111111111111",
94 | "4012888888881881"]
95 | visa.forEach {
96 | XCTAssert(CardState(fromPrefix: $0) == .identified(.visa))
97 | XCTAssert(CardState(fromNumber: $0) == .identified(.visa))
98 | }
99 | }
100 |
101 | func testInvalid() {
102 | XCTAssert(CardState(fromPrefix: "0") == .invalid)
103 | XCTAssert(CardState(fromNumber: "") == .invalid)
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/Example/Tests/ExpirationDateFormatterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExpirationDateFormatterTests.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/11/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import FrictionLess
11 |
12 | class ExpirationDateFormatterTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | }
17 |
18 | override func tearDown() {
19 | super.tearDown()
20 | }
21 |
22 | func testRemoveFormatting() {
23 | let set = CharacterSet.decimalDigits
24 | var input: String
25 | var output: String
26 | var expectedOutput: String
27 | var cursorPosition: Int
28 | var expectedCursorPosition: Int
29 |
30 | input = "03/20"
31 | expectedOutput = "0320"
32 | cursorPosition = 0 // |03/20
33 | expectedCursorPosition = 0
34 |
35 | output = input.filteringWith(characterSet:set)
36 | cursorPosition = input.position(ofCursorLocation: cursorPosition, in: output, within: set)
37 | XCTAssert(output == expectedOutput)
38 | XCTAssert(cursorPosition == expectedCursorPosition, "expected cursor position: \(expectedCursorPosition) got \(cursorPosition)")
39 |
40 | cursorPosition = 1 // 0|3/20
41 | expectedCursorPosition = 1
42 | _ = input.filteringWith(characterSet:set)
43 | cursorPosition = input.position(ofCursorLocation: cursorPosition, in: output, within: set)
44 | XCTAssert(cursorPosition == expectedCursorPosition, "expected cursor position: \(expectedCursorPosition) got \(cursorPosition)")
45 |
46 | cursorPosition = 3 //03/|20
47 | expectedCursorPosition = 2
48 | _ = input.filteringWith(characterSet:set)
49 | cursorPosition = input.position(ofCursorLocation: cursorPosition, in: output, within: set)
50 | XCTAssert(cursorPosition == expectedCursorPosition, "expected cursor position: \(expectedCursorPosition) got \(cursorPosition)")
51 |
52 | cursorPosition = 5 //03/20|
53 | expectedCursorPosition = 4
54 | _ = input.filteringWith(characterSet:set)
55 | cursorPosition = input.position(ofCursorLocation: cursorPosition, in: output, within: set)
56 | XCTAssert(cursorPosition == expectedCursorPosition, "expected cursor position: \(expectedCursorPosition) got \(cursorPosition)")
57 | }
58 |
59 | func testInsertFormatting() {
60 | var input: String
61 | var expectedOutput: String
62 | var expectedCursorPosition: Int
63 | let textField = FormattableTextField(formatter: ExpirationDateFormatter())
64 | textField.addToViewHiearchyAndBecomeFirstResponder()
65 |
66 | //leading with 1 could be Jan, Oct, Nov, Dec. Do not format
67 | input = "1"
68 | expectedOutput = "1"
69 | textField.addText(input, initialText: "", initialCursorPosition: 0, selectionLength: 0)
70 | XCTAssert(textField.text == expectedOutput, "expected \(expectedOutput) got \(textField.text ?? "")")
71 |
72 | //leading with 2 must be Feb, change to 02 and add slash
73 | input = "2"
74 | expectedOutput = "02/"
75 | textField.addText(input, initialText: "", initialCursorPosition: 0, selectionLength: 0)
76 | XCTAssert(textField.text == expectedOutput, "expected \(expectedOutput) got \(textField.text ?? "")")
77 | expectedCursorPosition = 3
78 | XCTAssert(textField.cursorOffset == expectedCursorPosition, "expected \(expectedCursorPosition) got \(textField.cursorOffset)")
79 |
80 | //leading with 0 is someone following instructions, do not format
81 | input = "0"
82 | expectedOutput = "0"
83 | textField.addText(input, initialText: "", initialCursorPosition: 0, selectionLength: 0)
84 | XCTAssert(textField.text == expectedOutput, "expected \(expectedOutput) got \(textField.text ?? "")")
85 |
86 | //if someone inputs a slash after a 1, pad the 0
87 | let initialText = "1"
88 | let initialCursor = 1
89 | input = "/"
90 | expectedOutput = "01/"
91 | textField.addText(input, initialText: initialText, initialCursorPosition: initialCursor, selectionLength: 0)
92 | XCTAssert(textField.text == expectedOutput, "expected \(expectedOutput) got \(textField.text ?? "")")
93 |
94 | //add a slash for valid 2 digit months
95 | input = "10"
96 | expectedOutput = "10/"
97 | textField.addText(input, initialText: "", initialCursorPosition: 0, selectionLength: 0)
98 | XCTAssert(textField.text == expectedOutput, "expected \(expectedOutput) got \(textField.text ?? "")")
99 | expectedCursorPosition = 3
100 | XCTAssert(textField.cursorOffset == expectedCursorPosition, "expected \(expectedCursorPosition) got \(textField.cursorOffset)")
101 |
102 | //if input carries over max, reject
103 | input = "12/345"
104 | expectedOutput = ""
105 | textField.addText(input, initialText: "", initialCursorPosition: 0, selectionLength: 0)
106 | XCTAssert(textField.text == expectedOutput, "expected \(expectedOutput) got \(textField.text ?? "")")
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/Example/Tests/FormattableTextFieldTestHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormattableTextFieldTestHelpers.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 9/27/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import FrictionLess
11 |
12 | // MARK: - Helper Methods
13 | extension FormattableTextField {
14 |
15 | func addText(_ textToAdd: String, initialText: String, initialCursorPosition: Int, selectionLength: Int) {
16 | //set original state
17 | text = initialText
18 | selectedTextRange = textRangeForCursorPosition(initialCursorPosition, length: selectionLength)
19 |
20 | //simulate adding text
21 | self.simulateInput(text: textToAdd, range: NSRange(location: initialCursorPosition, length: selectionLength))
22 | }
23 |
24 | func deleteFromInitialText(_ initialText: String, initialCursorPosition: Int, numToDelete: Int) {
25 | text = initialText
26 | selectedTextRange = textRangeForCursorPosition(initialCursorPosition - (numToDelete - 1), length: (numToDelete - 1))
27 | let deleteRange = NSRange(location: initialCursorPosition-numToDelete, length: numToDelete)
28 | //simulate adding text
29 | self.simulateInput(text: "", range: deleteRange)
30 | }
31 |
32 | func textRangeForCursorPosition(_ cursorPosition: Int, length: Int) -> UITextRange? {
33 | guard let startPosition = position(from: beginningOfDocument, offset: cursorPosition),
34 | let endPosition = position(from: beginningOfDocument, offset: cursorPosition + length) else {
35 | return nil
36 | }
37 | return textRange(from: startPosition, to: endPosition)
38 | }
39 |
40 | func addToViewHiearchyAndBecomeFirstResponder() {
41 | let window = UIWindow()
42 | let view = UIView()
43 | window.addSubview(view)
44 | view.addSubview(self)
45 | becomeFirstResponder()
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Example/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/FrictionLess.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'FrictionLess'
3 | s.version = '0.0.1'
4 | s.summary = 'A collection of UX-focused swift components for reducing friction in "user work".'
5 | s.description = <<-DESC
6 | Reduce friction with auto-formatting data entry, auto-advancing forms, and proactive user feedback for valid/invalid input.
7 | DESC
8 |
9 | s.homepage = 'https://github.com/Raizlabs/FrictionLess'
10 | s.license = { :type => 'MIT', :file => 'LICENSE' }
11 | s.author = { 'Jay Clark' => 'jason.clark@raizlabs.com' }
12 | s.source = { :git => 'https://github.com/raizlabs/FrictionLess.git', :tag => s.version.to_s }
13 |
14 | s.platform = :ios, '10.0'
15 |
16 | s.default_subspec = 'All'
17 |
18 | # FormattableTextField
19 |
20 | s.subspec "FormattableTextField" do |ss|
21 | ss.source_files = 'FrictionLess/FormattableTextField/**/*'
22 | ss.frameworks = ["UIKit"]
23 | end
24 |
25 | # Card Entry
26 |
27 | s.subspec "CardEntry" do |ss|
28 | ss.source_files = 'FrictionLess/CardEntry/**/*.{swift,strings}'
29 | ss.dependency 'FrictionLess/FormUI'
30 | ss.resources = "FrictionLess/CardEntry/CardEntry.xcassets"
31 | end
32 |
33 | # Phone Number Formatter
34 |
35 | s.subspec "PhoneFormatter" do |ss|
36 | ss.source_files = 'FrictionLess/PhoneFormatter/**/*'
37 | ss.dependency 'FrictionLess/FormattableTextField'
38 | ss.dependency 'PhoneNumberKit'
39 | end
40 |
41 | # Form UI
42 |
43 | s.subspec "FormUI" do |ss|
44 | ss.source_files = 'FrictionLess/FormUI/**/*.{swift,strings}'
45 | ss.dependency 'Anchorage'
46 | ss.dependency 'FrictionLess/FormattableTextField'
47 | end
48 |
49 | # Catch All
50 |
51 | s.subspec "All" do |ss|
52 | ss.dependency 'FrictionLess/FormattableTextField'
53 | ss.dependency 'FrictionLess/CardEntry'
54 | ss.dependency 'FrictionLess/PhoneFormatter'
55 | ss.dependency 'FrictionLess/FormUI'
56 | end
57 |
58 | end
59 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | FrictionLess
4 |
5 | Created by Jason Clark on 7/18/17.
6 |
7 | */
8 |
9 | "FrictionLess.CardEntry.CardType.Amex" = "American Express";
10 | "FrictionLess.CardEntry.CardType.Visa" = "Visa";
11 | "FrictionLess.CardEntry.CardType.MasterCard" = "MasterCard";
12 | "FrictionLess.CardEntry.CardType.Discover" = "Discover";
13 | "FrictionLess.CardEntry.CardType.Diners" = "Diners Club";
14 | "FrictionLess.CardEntry.CardType.JCB" = "JCB";
15 |
16 | "FrictionLess.CardEntry.CardNumber.Title" = "Credit Card Number";
17 | "FrictionLess.CardEntry.CardNumber.Placeholder" = "0000 0000 0000 0000";
18 | "FrictionLess.CardEntry.Expiration.Title" = "Expiration";
19 | "FrictionLess.CardEntry.Expiration.Placeholder" = "MM/YY";
20 | "FrictionLess.CardEntry.CVV.Title" = "CVV";
21 | "FrictionLess.CardEntry.CVV.Placeholder" = "123";
22 | "FrictionLess.CardEntry.CVV.AmexPlaceholder" = "1234";
23 |
24 | "FrictionLess.CardEntry.Validation.NotAccepted" = "%@ Not Accepted";
25 | "FrictionLess.CardEntry.Validation.NotAccepted.Generic" = "Not Accepted";
26 | "FrictionLess.CardEntry.Validation.Expired" = "Date Expired";
27 | "FrictionLess.CardEntry.Validation.CardNumberInvalid" = "Card Number Invalid";
28 | "FrictionLess.CardEntry.Validation.ExpirationInvalid" = "Date Invalid or Expired";
29 | "FrictionLess.CardEntry.Validation.CVVInvalid" = "Invalid";
30 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/CameraScan.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-scan.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/CameraScan.imageset/icn-cc-scan.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/CameraScan.imageset/icn-cc-scan.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/back.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-back-ccv.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/back.imageset/icn-cc-back-ccv.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/back.imageset/icn-cc-back-ccv.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/front.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-amex-ccv.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/front.imageset/icn-cc-amex-ccv.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/CVV/front.imageset/icn-cc-amex-ccv.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/americanexpress.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-amex.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/americanexpress.imageset/icn-cc-amex.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/americanexpress.imageset/icn-cc-amex.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/diners.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-dinersclub.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/diners.imageset/icn-cc-dinersclub.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/diners.imageset/icn-cc-dinersclub.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/discover.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-discover.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/discover.imageset/icn-cc-discover.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/discover.imageset/icn-cc-discover.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/jcb.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-jcb.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/jcb.imageset/icn-cc-jcb.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/jcb.imageset/icn-cc-jcb.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/mastercard.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-mastercard.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/mastercard.imageset/icn-cc-mastercard.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/mastercard.imageset/icn-cc-mastercard.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/notAccepted.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-placeholder-not-accepted.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/notAccepted.imageset/icn-cc-placeholder-not-accepted.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/notAccepted.imageset/icn-cc-placeholder-not-accepted.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/placeholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-placeholder.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/placeholder.imageset/icn-cc-placeholder.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/placeholder.imageset/icn-cc-placeholder.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/visa.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icn-cc-visa.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/visa.imageset/icn-cc-visa.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rightpoint/FrictionLess/b4696433ef99c926d797c8a92be9108ef65fac10/FrictionLess/CardEntry/CardEntry.xcassets/Credit Card/visa.imageset/icn-cc-visa.pdf
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CardImageViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardImageViewState.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 4/5/17.
6 | //
7 | //
8 |
9 | import UIKit
10 |
11 | public struct CardImageViewState {
12 |
13 | var imageState: CardImageState
14 |
15 | public init(imageState: CardImageState) {
16 | self.imageState = imageState
17 | }
18 |
19 | func transition(from oldState: CardImageState, to newState: CardImageState) -> UIViewAnimationOptions {
20 | var transition = UIViewAnimationOptions.transitionCrossDissolve
21 | switch (oldState, newState) {
22 |
23 | //transition between two accepted or ambiguous card states
24 | case (.card(let old), .card(let new)) where old.isAccepted && new.isAccepted:
25 | switch (old.cardState, new.cardState) {
26 |
27 | //from unidentified to identfied
28 | case (.indeterminate, .identified): transition = .transitionFlipFromRight
29 |
30 | //from identified to unidentified
31 | case (.identified, .indeterminate): transition = .transitionFlipFromLeft
32 | default: break
33 | }
34 |
35 | //transition between card states where one is not accepted
36 | case (.card(let old), .card(let new)) where !(old.isAccepted && new.isAccepted):
37 | transition = .transitionCrossDissolve
38 |
39 | //transition between card and cvv
40 | case (.card, .cvv(let newCard)):
41 | switch newCard.cardState.cvvLocation {
42 |
43 | //card to front cvv, no flip
44 | case .front: transition = .transitionCrossDissolve
45 |
46 | //card to rear cvv, flip from right
47 | case .back: transition = .transitionFlipFromRight
48 | }
49 |
50 | //transition between cvv and card
51 | case (.cvv(let oldCard), .card):
52 | switch oldCard.cardState.cvvLocation {
53 |
54 | //front cvv to card, no flip
55 | case .front: transition = .transitionCrossDissolve
56 |
57 | //rear cvv to card, flip from left
58 | case .back: transition = .transitionFlipFromLeft
59 | }
60 | default:
61 | break
62 | }
63 | return transition
64 | }
65 |
66 | }
67 |
68 | public enum CardImageState {
69 | case card(creditCard: CardEntryViewState)
70 | case cvv(creditCard: CardEntryViewState)
71 |
72 | public var image: UIImage {
73 | switch self {
74 | case .card(let card):
75 | return card.isAccepted ? card.cardState.image : Images.CreditCard.notAccepted.image
76 | case .cvv(let card): return card.cardState.cvvImage
77 | }
78 | }
79 | }
80 |
81 | extension CardImageState: Equatable {
82 | public static func == (lhs: CardImageState, rhs: CardImageState) -> Bool {
83 | switch (lhs, rhs) {
84 | case (.card(let card1), .card(let card2)):
85 | return (card1.cardState == card2.cardState) && (card1.isAccepted == card2.isAccepted)
86 | case (.cvv(let card1), .cvv(let card2)):
87 | return (card1.cardState == card2.cardState) && (card1.isAccepted == card2.isAccepted)
88 | default: return false
89 | }
90 | }
91 | }
92 |
93 | private extension CardState {
94 |
95 | var image: UIImage {
96 | switch self {
97 | case .identified(let cardType): return cardType.image
98 | case .invalid: return Images.CreditCard.notAccepted.image
99 | case .indeterminate: return Images.CreditCard.placeholder.image
100 | }
101 | }
102 |
103 | var cvvImage: UIImage {
104 | switch self {
105 | case .identified(let cardType): return cardType.cvvImage
106 | default: return Images.CreditCard.Cvv.back.image
107 | }
108 | }
109 |
110 | var cvvLocation: CVVLocation {
111 | switch self {
112 | case .identified(let cardType): return cardType.cvvLocation
113 | default: return .back
114 | }
115 | }
116 |
117 | }
118 |
119 | private enum CVVLocation {
120 | case front
121 | case back
122 | }
123 |
124 | private extension CardType {
125 |
126 | var image: UIImage {
127 | switch self {
128 | case .visa: return Images.CreditCard.visa.image
129 | case .masterCard: return Images.CreditCard.mastercard.image
130 | case .amex: return Images.CreditCard.americanexpress.image
131 | case .discover: return Images.CreditCard.discover.image
132 | case .diners: return Images.CreditCard.diners.image
133 | case .jcb: return Images.CreditCard.jcb.image
134 | }
135 | }
136 |
137 | var cvvImage: UIImage {
138 | switch self {
139 | case .amex: return Images.CreditCard.Cvv.front.image
140 | default: return Images.CreditCard.Cvv.back.image
141 | }
142 | }
143 |
144 | var cvvLocation: CVVLocation {
145 | switch self {
146 | case .amex: return .front
147 | default: return .back
148 | }
149 | }
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/CreditEntryViewState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardEntryViewState.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 3/29/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public struct CardEntryViewState {
12 |
13 | let acceptedCardTypes: [CardType] = [.masterCard, .visa, .discover, .amex]
14 | var number: String = ""
15 | var expiration: String = ""
16 | var cvv: String = ""
17 |
18 | mutating func update(number: String, expiration: String, cvv: String) {
19 | if self.number != number {
20 | self.number = number
21 | }
22 | if self.expiration != expiration {
23 | self.expiration = expiration
24 | }
25 | if self.cvv != cvv {
26 | self.cvv = cvv
27 | }
28 | }
29 |
30 | public init() {}
31 |
32 | }
33 |
34 | public extension CardEntryViewState {
35 |
36 | var cardState: CardState {
37 | return CardState(fromPrefix: number)
38 | }
39 |
40 | var isAccepted: Bool {
41 | if case .identified(let card) = cardState,
42 | !acceptedCardTypes.contains(card) {
43 | return false
44 | }
45 | else {
46 | return true
47 | }
48 | }
49 |
50 | }
51 |
52 | public extension CardType {
53 |
54 | var name: String {
55 | switch self {
56 | case .amex: return Strings.Frictionless.Cardentry.Cardtype.amex
57 | case .diners: return Strings.Frictionless.Cardentry.Cardtype.diners
58 | case .discover: return Strings.Frictionless.Cardentry.Cardtype.discover
59 | case .jcb: return Strings.Frictionless.Cardentry.Cardtype.jcb
60 | case .masterCard: return Strings.Frictionless.Cardentry.Cardtype.masterCard
61 | case .visa: return Strings.Frictionless.Cardentry.Cardtype.visa
62 | }
63 | }
64 |
65 | }
66 |
67 | public extension CardEntryViewState {
68 |
69 | var notAcceptedErrorMessage: String {
70 | if case .identified(let card) = cardState {
71 | return Strings.Frictionless.Cardentry.Validation.notAccepted(card.name)
72 | }
73 | else {
74 | return Strings.Frictionless.Cardentry.Validation.Notaccepted.generic
75 | }
76 | }
77 |
78 | func errorString(forFormatter formatter: TextFieldFormatter?, error: Error) -> String? {
79 | switch formatter {
80 | case is CreditCardFormatter:
81 | if !isAccepted {
82 | return notAcceptedErrorMessage
83 | }
84 | else {
85 | switch error {
86 | case FormattableTextFieldError.invalidInput: break
87 | case CreditCardFormatterError.invalidCardNumber: return Strings.Frictionless.Cardentry.Validation.cardNumberInvalid
88 | case CreditCardFormatterError.maxLengthExceeded: break
89 | default: break
90 | }
91 | }
92 |
93 | case is ExpirationDateFormatter:
94 | switch error {
95 | case FormattableTextFieldError.invalidInput: break
96 | case ExpirationDateFormatterError.expired: return Strings.Frictionless.Cardentry.Validation.expired
97 | case ExpirationDateFormatterError.invalidMonth: break
98 | case ExpirationDateFormatterError.invalidYear: return Strings.Frictionless.Cardentry.Validation.expirationInvalid
99 | case ExpirationDateFormatterError.maxLength: break
100 | default: break
101 | }
102 |
103 | case is CVVFormatter:
104 | switch error {
105 | case FormattableTextFieldError.invalidInput: break
106 | case CVVFormatterError.maxLength: break
107 | default: break
108 | }
109 |
110 | default: break
111 | }
112 | return nil
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/Formatters/CVVFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CVVFormatter.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/21/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum CVVFormatterError: Error {
12 | case minLength
13 | case maxLength
14 | }
15 |
16 | public struct CVVFormatter: TextFieldFormatter {
17 |
18 | var requiredLength = 3
19 |
20 | public var inputCharacterSet = CharacterSet.decimalDigits
21 |
22 | public func validate(_ string: String) -> ValidationResult {
23 | let length = string.characters.count
24 | if length < requiredLength {
25 | return .invalid(validationError: CVVFormatterError.minLength)
26 | }
27 | else if length > requiredLength {
28 | return .invalid(validationError: CVVFormatterError.maxLength)
29 | }
30 | else {
31 | return .valid
32 | }
33 | }
34 |
35 | public func format(editingEvent: EditingEvent) -> FormattingResult {
36 | if editingEvent.newValue.characters.count <= requiredLength {
37 | return .valid(nil)
38 | }
39 | else {
40 | return .invalid(formattingError: CVVFormatterError.maxLength)
41 | }
42 | }
43 |
44 | public func isComplete(_ text: String) -> Bool {
45 | if case .valid = validate(text) {
46 | return true
47 | }
48 | return false
49 | }
50 |
51 | public init() {}
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/Formatters/CreditCardFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CreditCardFormatter.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/21/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum CreditCardFormatterError: Error {
12 | case maxLengthExceeded
13 | case invalidCardNumber
14 | }
15 |
16 | public struct CreditCardFormatter: TextFieldFormatter {
17 |
18 | public var inputCharacterSet = CharacterSet.decimalDigits
19 | public var formattingCharacterSet = CharacterSet.whitespaces
20 |
21 | public func format(editingEvent: EditingEvent) -> FormattingResult {
22 | let newCardNumber = editingEvent.newValue
23 | let newCardState = CardState(fromPrefix: newCardNumber)
24 |
25 | let result: FormattingResult = {
26 | switch newCardState {
27 | case .invalid:
28 | return .invalid(formattingError: CreditCardFormatterError.invalidCardNumber)
29 | case .indeterminate(_):
30 | return .valid(nil)
31 | case .identified(let card):
32 | let cardLength = newCardNumber.characters.count
33 | guard cardLength <= card.maxLength else {
34 | return .invalid(formattingError: CreditCardFormatterError.maxLengthExceeded)
35 | }
36 | if cardLength == card.maxLength && !card.isValid(newCardNumber) {
37 | return .invalid(formattingError: CreditCardFormatterError.invalidCardNumber)
38 | }
39 | let formatted = newCardNumber.inserting(" ", formingGroupings: card.segmentGroupings)
40 | return .valid(.text(formatted))
41 | }
42 | }()
43 |
44 | return result
45 | }
46 |
47 | public func validate(_ string: String) -> ValidationResult {
48 | if case CardState.identified(let card) = CardState(fromNumber: string) {
49 | if card.isValid(string) {
50 | return .valid
51 | }
52 | }
53 | return .invalid(validationError: CreditCardFormatterError.invalidCardNumber)
54 | }
55 |
56 | public func isComplete(_ text: String) -> Bool {
57 | return validate(text) == .valid
58 | }
59 |
60 | public init() {}
61 |
62 | }
63 |
64 | extension String {
65 |
66 | //TODO: Can replace this with something simpler
67 | func inserting(_ formattingString: String, formingGroupings groupings: [Int]) -> String {
68 | var formattedString = String()
69 | let formattingIndicies = groupings.dropLast().reduce([], { sums, element in
70 | return sums + [element + (sums.last ?? -1)]
71 | })
72 |
73 | characters.enumerated().forEach { offset, character in
74 | formattedString.append(character)
75 | if formattingIndicies.contains(offset) {
76 | formattedString.append(formattingString)
77 | }
78 | }
79 | return formattedString
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/Formatters/ExpirationDateFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExpirationDateFormatter.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/21/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum ExpirationDateFormatterError: Error {
12 | case maxLength
13 | case minLength
14 | case invalidMonth
15 | case invalidYear
16 | case expired
17 | }
18 |
19 | public struct ExpirationDateFormatter: TextFieldFormatter {
20 |
21 | let requiredLength = 4
22 | let validFutureExpYearRange = 30
23 |
24 | public var inputCharacterSet: CharacterSet = .decimalDigits
25 | public var formattingCharacterSet = CharacterSet(charactersIn: "/")
26 | public var deletingShouldRemoveTrailingCharacters = true
27 |
28 | public func validate(_ string: String) -> ValidationResult {
29 | let unformatted = removeFormatting(string)
30 | let length = unformatted.characters.count
31 | if length < requiredLength {
32 | return .invalid(validationError: ExpirationDateFormatterError.minLength)
33 | }
34 | else if length > requiredLength {
35 | return .invalid(validationError: ExpirationDateFormatterError.maxLength)
36 | }
37 |
38 | return validate(expDate: unformatted)
39 | }
40 |
41 | public func format(editingEvent: EditingEvent) -> FormattingResult {
42 | var newExpirationDate = editingEvent.newValue
43 | guard newExpirationDate.characters.count <= requiredLength else {
44 | return .invalid(formattingError: ExpirationDateFormatterError.maxLength)
45 | }
46 |
47 | //If user manually enters formatting character after a 1, pad with a leading 0
48 | if editingEvent.editRange.location == 1 && editingEvent.editString == "/" {
49 | newExpirationDate.insert("0", at: newExpirationDate.startIndex)
50 | }
51 |
52 | let formatted = formatString(newExpirationDate)
53 |
54 | switch validate(expDate: removeFormatting(formatted)) {
55 | case .valid:
56 | return .valid(.text(formatted))
57 | case .invalid(let error):
58 | return .invalid(formattingError: error)
59 | }
60 | }
61 |
62 | public func isComplete(_ text: String) -> Bool {
63 | return validate(text) == .valid
64 | }
65 |
66 | public init() {}
67 |
68 | }
69 |
70 | private extension ExpirationDateFormatter {
71 |
72 | func formatString(_ text: String) -> String {
73 | var formattedString = String()
74 |
75 | for (index, character) in text.characters.enumerated() {
76 | if index == 0 && text.characters.count == 1 && "2"..."9" ~= character {
77 | formattedString.append("0\(character)/")
78 | }
79 | else if index == 1 && text.characters.count == 2 && text.characters.first == "1"
80 | && !("1"..."2" ~= character) && validYearRanges.contains(where: { $0.hasCommonPrefix(with: String(character)) }) {
81 | //digit after leading 1 is not a valid month but is the start of a valid year.
82 | formattedString.insert("0", at: formattedString.startIndex)
83 | formattedString.append("/\(character)")
84 | }
85 | else {
86 | formattedString.append(character)
87 | if index == 1 {
88 | formattedString.append("/")
89 | }
90 | }
91 | }
92 |
93 | return formattedString
94 | }
95 |
96 | }
97 |
98 | private extension ExpirationDateFormatter {
99 |
100 | var currentYearSuffix: Int {
101 | let fullYear = (Calendar.current as NSCalendar).component(.year, from: Date())
102 | return fullYear % 100
103 | }
104 |
105 | var currentMonth: Int {
106 | return (Calendar.current as NSCalendar).component(.month, from: Date())
107 | }
108 |
109 | var validYearRanges: [ClosedRange] {
110 | let shortYear = currentYearSuffix
111 | var endYear = shortYear + validFutureExpYearRange
112 | if endYear < 100 {
113 | return [String(shortYear)...String(endYear)]
114 | }
115 | else {
116 | endYear = endYear % 100
117 | return [String(shortYear)..."99",
118 | "00"...String(endYear)]
119 | }
120 | }
121 |
122 | func validate(expDate: String) -> ValidationResult {
123 | guard expDate.characters.count > 0 else {
124 | return .valid
125 | }
126 | let monthsRange = "01"..."12"
127 | guard monthsRange.hasCommonPrefix(with: expDate) else {
128 | return .invalid(validationError: ExpirationDateFormatterError.invalidMonth)
129 | }
130 | //months valid, check year
131 | guard expDate.characters.count > 2 else {
132 | return .valid
133 | }
134 | let separatorIndex = expDate.characters.index(expDate.startIndex, offsetBy: 2)
135 | let monthString = expDate.substring(to: separatorIndex)
136 | let yearSuffixString = expDate.substring(from: separatorIndex)
137 | guard validYearRanges.contains(where: { $0.hasCommonPrefix(with: yearSuffixString) }) else {
138 | if yearSuffixString < String(currentYearSuffix) {
139 | return .invalid(validationError: ExpirationDateFormatterError.expired)
140 | }
141 | else {
142 | return .invalid(validationError: ExpirationDateFormatterError.invalidYear)
143 | }
144 | }
145 | //year valid, check month year combo
146 | guard String(currentYearSuffix).hasCommonPrefix(with: yearSuffixString) else {
147 | //If a future year, we don't have to check month
148 | return .valid
149 | }
150 | guard !(yearSuffixString.characters.count == 1 && String(currentYearSuffix + 1).hasCommonPrefix(with: yearSuffixString)) else {
151 | //year is incomplete and can potentially be a future year
152 | return .valid
153 | }
154 |
155 | if String(currentMonth) < monthString {
156 | return .invalid(validationError: ExpirationDateFormatterError.expired)
157 | }
158 |
159 | return .valid
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/Formatters/ZipFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZipFormatter.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/21/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum ZipFormatterError: Error {
12 | case minLength
13 | case maxLength
14 | }
15 |
16 | public struct ZipFormatter: TextFieldFormatter {
17 |
18 | var requiredLength = 5
19 |
20 | public var inputCharacterSet = CharacterSet.decimalDigits
21 |
22 | public func validate(_ string: String) -> ValidationResult {
23 | let length = string.characters.count
24 | if length < requiredLength {
25 | return .invalid(validationError: ZipFormatterError.minLength)
26 | }
27 | else if length > requiredLength {
28 | return .invalid(validationError: ZipFormatterError.maxLength)
29 | }
30 | else {
31 | return .valid
32 | }
33 | }
34 |
35 | public func format(editingEvent: EditingEvent) -> FormattingResult {
36 | if editingEvent.newValue.characters.count <= requiredLength {
37 | return .valid(nil)
38 | }
39 | else {
40 | return .invalid(formattingError: ZipFormatterError.maxLength)
41 | }
42 | }
43 |
44 | public func isComplete(_ text: String) -> Bool {
45 | return validate(text) == .valid
46 | }
47 |
48 | public init() {}
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/README.md:
--------------------------------------------------------------------------------
1 | # Card Entry
2 | A drop-in, customizable card entry widget.
3 |
4 | 
5 |
6 | # Usage
7 | See [CardEntryExampleViewController](https://github.com/Raizlabs/FrictionLess/blob/develop/Example/FrictionLess/CardEntryExampleViewController.swift) in the example project.
8 |
9 | To implement, add the `CardEntryViewController` as a child to your `UIViewController`:
10 | ```swift
11 | // in some view controller
12 |
13 | let cardVC = CardEntryViewController()
14 | cardVC.delegate = self
15 | view.addSubview(cardVC.view)
16 | addChildViewController(cardVC)
17 | cardVC.didMove(toParentViewController: self)
18 | ```
19 | ### CardEntryViewControllderDelegate
20 | Assign the delegate to receive validity callbacks:
21 | ```swift
22 | func cardEntryViewController(_ vc: CardEntryViewController, creditCardValid: Bool)
23 | ```
24 | ### Configuration
25 | Leverage `UIAppearance` or instance properties to tweak styling.
26 | ```swift
27 | CardEntryView.appearance().cornerRadius = 5
28 | CardEntryView.appearance().backgroundColor = .lighGray
29 | CardEntryView.layoutMargins = UIEdgeInsets(top: 10, left: 50, bottom: 10, right: 10)
30 |
31 | FormattableTextField.appearance().font = UIFont.preferredFont(forTextStyle: .body)
32 | FormattableTextField.appearance().textColor = .darkText
33 |
34 | FrictionLessFormComponent.appearance().titleToTextFieldPadding = 3
35 | FrictionLessFormComponent.appearance().textFieldToValidationPadding = 2
36 |
37 | FrictionLessFormValidationLabel.appearance().textColor = .red
38 | ```
39 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/Strings+CardEntry.swift:
--------------------------------------------------------------------------------
1 | // Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen
2 |
3 | import Foundation
4 |
5 | // swiftlint:disable file_length
6 | // swiftlint:disable line_length
7 |
8 | // swiftlint:disable type_body_length
9 | // swiftlint:disable nesting
10 | // swiftlint:disable variable_name
11 | // swiftlint:disable valid_docs
12 | // swiftlint:disable type_name
13 |
14 | enum Strings {
15 |
16 | enum Frictionless {
17 |
18 | enum Cardentry {
19 |
20 | enum Cardnumber {
21 | /// 0000 0000 0000 0000
22 | static let placeholder = Strings.tr("FrictionLess.CardEntry.CardNumber.Placeholder")
23 | /// Credit Card Number
24 | static let title = Strings.tr("FrictionLess.CardEntry.CardNumber.Title")
25 | }
26 |
27 | enum Cardtype {
28 | /// American Express
29 | static let amex = Strings.tr("FrictionLess.CardEntry.CardType.Amex")
30 | /// Diners Club
31 | static let diners = Strings.tr("FrictionLess.CardEntry.CardType.Diners")
32 | /// Discover
33 | static let discover = Strings.tr("FrictionLess.CardEntry.CardType.Discover")
34 | /// JCB
35 | static let jcb = Strings.tr("FrictionLess.CardEntry.CardType.JCB")
36 | /// MasterCard
37 | static let masterCard = Strings.tr("FrictionLess.CardEntry.CardType.MasterCard")
38 | /// Visa
39 | static let visa = Strings.tr("FrictionLess.CardEntry.CardType.Visa")
40 | }
41 |
42 | enum Cvv {
43 | /// 1234
44 | static let amexPlaceholder = Strings.tr("FrictionLess.CardEntry.CVV.AmexPlaceholder")
45 | /// 123
46 | static let placeholder = Strings.tr("FrictionLess.CardEntry.CVV.Placeholder")
47 | /// CVV
48 | static let title = Strings.tr("FrictionLess.CardEntry.CVV.Title")
49 | }
50 |
51 | enum Expiration {
52 | /// MM/YY
53 | static let placeholder = Strings.tr("FrictionLess.CardEntry.Expiration.Placeholder")
54 | /// Expiration
55 | static let title = Strings.tr("FrictionLess.CardEntry.Expiration.Title")
56 | }
57 |
58 | enum Validation {
59 | /// Card Number Invalid
60 | static let cardNumberInvalid = Strings.tr("FrictionLess.CardEntry.Validation.CardNumberInvalid")
61 | /// Invalid
62 | static let cvvInvalid = Strings.tr("FrictionLess.CardEntry.Validation.CVVInvalid")
63 | /// Date Invalid or Expired
64 | static let expirationInvalid = Strings.tr("FrictionLess.CardEntry.Validation.ExpirationInvalid")
65 | /// Date Expired
66 | static let expired = Strings.tr("FrictionLess.CardEntry.Validation.Expired")
67 | /// %@ Not Accepted
68 | static func notAccepted(_ p1: String) -> String {
69 | return Strings.tr("FrictionLess.CardEntry.Validation.NotAccepted", p1)
70 | }
71 |
72 | enum Notaccepted {
73 | /// Not Accepted
74 | static let generic = Strings.tr("FrictionLess.CardEntry.Validation.NotAccepted.Generic")
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | extension Strings {
82 | fileprivate static func tr(_ key: String, _ args: CVarArg...) -> String {
83 | let format = NSLocalizedString(key, tableName: "CardEntry", bundle: Bundle(for: BundleToken.self), comment: "")
84 | return String(format: format, locale: Locale.current, arguments: args)
85 | }
86 | }
87 |
88 | private final class BundleToken {}
89 |
90 | // swiftlint:enable type_body_length
91 | // swiftlint:enable nesting
92 | // swiftlint:enable variable_name
93 | // swiftlint:enable valid_docs
94 |
--------------------------------------------------------------------------------
/FrictionLess/CardEntry/UIImage+CardEntry.swift:
--------------------------------------------------------------------------------
1 | // Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen
2 |
3 | #if os(iOS) || os(tvOS) || os(watchOS)
4 | import UIKit.UIImage
5 | typealias Image = UIImage
6 | #elseif os(OSX)
7 | import AppKit.NSImage
8 | typealias Image = NSImage
9 | #endif
10 |
11 | // swiftlint:disable file_length
12 | // swiftlint:disable line_length
13 | // swiftlint:disable nesting
14 |
15 | struct ImagesType: ExpressibleByStringLiteral {
16 | fileprivate var value: String
17 |
18 | var image: Image {
19 | let bundle = Bundle(for: BundleToken.self)
20 | #if os(iOS) || os(tvOS)
21 | let image = Image(named: value, in: bundle, compatibleWith: nil)
22 | #elseif os(OSX)
23 | let image = bundle.image(forResource: value)
24 | #elseif os(watchOS)
25 | let image = Image(named: value)
26 | #endif
27 | guard let result = image else { fatalError("Unable to load image \(value).") }
28 | return result
29 | }
30 |
31 | init(stringLiteral value: String) {
32 | self.value = value
33 | }
34 |
35 | init(extendedGraphemeClusterLiteral value: String) {
36 | self.init(stringLiteral: value)
37 | }
38 |
39 | init(unicodeScalarLiteral value: String) {
40 | self.init(stringLiteral: value)
41 | }
42 | }
43 |
44 | // swiftlint:disable type_body_length
45 | enum Images {
46 | static let cameraScan: ImagesType = "CameraScan"
47 | enum CreditCard {
48 | static let americanexpress: ImagesType = "americanexpress"
49 | enum Cvv {
50 | static let back: ImagesType = "back"
51 | static let front: ImagesType = "front"
52 | }
53 | static let diners: ImagesType = "diners"
54 | static let discover: ImagesType = "discover"
55 | static let jcb: ImagesType = "jcb"
56 | static let mastercard: ImagesType = "mastercard"
57 | static let notAccepted: ImagesType = "notAccepted"
58 | static let placeholder: ImagesType = "placeholder"
59 | static let visa: ImagesType = "visa"
60 | }
61 | }
62 | // swiftlint:enable type_body_length
63 |
64 | extension Image {
65 | convenience init!(asset: ImagesType) {
66 | #if os(iOS) || os(tvOS)
67 | let bundle = Bundle(for: BundleToken.self)
68 | self.init(named: asset.value, in: bundle, compatibleWith: nil)
69 | #elseif os(OSX) || os(watchOS)
70 | self.init(named: asset.value)
71 | #endif
72 | }
73 | }
74 |
75 | private final class BundleToken {}
76 |
--------------------------------------------------------------------------------
/FrictionLess/FormUI/FormComponent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormComponent.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 7/19/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public protocol FormComponent {
12 |
13 | var title: String? { get set }
14 | var placeholder: String? { get set }
15 | var state: FormComponentState { get set }
16 | var isEnabled: Bool { get set }
17 | var textField: FormattableTextField { get }
18 |
19 | }
20 |
21 | public extension FormComponent {
22 |
23 | var value: String {
24 | get {
25 | return textField.unformattedText
26 | }
27 | set {
28 | if newValue != textField.unformattedText {
29 | textField.setTextAndFormat(text: newValue)
30 | }
31 | }
32 | }
33 |
34 | }
35 |
36 | public enum FormComponentState {
37 | case inactive
38 | case active
39 | case valid
40 | case invalid(errorString: String?)
41 | }
42 |
43 | public extension FormComponentState {
44 |
45 | var errorMessage: String? {
46 | if case .invalid(let error) = self {
47 | return error
48 | }
49 | else { return nil }
50 | }
51 |
52 | }
53 |
54 | public extension FormComponentState {
55 |
56 | static func == (lhs: FormComponentState, rhs: FormComponentState) -> Bool {
57 | switch (lhs, rhs) {
58 | case (.inactive, .inactive): return true
59 | case (.active, .active): return true
60 | case (.valid, .valid): return true
61 | case (.invalid(let e1), .invalid(let e2)):
62 | return e1 == e2
63 | default: return false
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/FrictionLess/FormUI/FormUI.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | FrictionLess
4 |
5 | Created by Jason Clark on 7/18/17.
6 |
7 | */
8 |
9 | "FrictionLess.FormUI.Validation.Required" = "%@ Required";
10 | "FrictionLess.FormUI.Validation.Invalid" = "%@ Invalid";
11 |
--------------------------------------------------------------------------------
/FrictionLess/FormUI/FrictionLessFormUIStrings.swift:
--------------------------------------------------------------------------------
1 | // Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen
2 |
3 | import Foundation
4 |
5 | // swiftlint:disable file_length
6 | // swiftlint:disable line_length
7 |
8 | // swiftlint:disable type_body_length
9 | // swiftlint:disable nesting
10 | // swiftlint:disable variable_name
11 | // swiftlint:disable valid_docs
12 | // swiftlint:disable type_name
13 |
14 | enum FrictionLessFormUIStrings {
15 |
16 | enum Frictionless {
17 |
18 | enum Formui {
19 |
20 | enum Validation {
21 | /// %@ Invalid
22 | static func invalid(_ p1: String) -> String {
23 | return FrictionLessFormUIStrings.tr("FrictionLess.FormUI.Validation.Invalid", p1)
24 | }
25 | /// %@ Required
26 | static func `required`(_ p1: String) -> String {
27 | return FrictionLessFormUIStrings.tr("FrictionLess.FormUI.Validation.Required", p1)
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
34 | extension FrictionLessFormUIStrings {
35 | fileprivate static func tr(_ key: String, _ args: CVarArg...) -> String {
36 | let format = NSLocalizedString(key, tableName: "FormUI", bundle: Bundle(for: BundleToken.self), comment: "")
37 | return String(format: format, locale: Locale.current, arguments: args)
38 | }
39 | }
40 |
41 | private final class BundleToken {}
42 |
43 | // swiftlint:enable type_body_length
44 | // swiftlint:enable nesting
45 | // swiftlint:enable variable_name
46 | // swiftlint:enable valid_docs
47 |
--------------------------------------------------------------------------------
/FrictionLess/FormUI/README.md:
--------------------------------------------------------------------------------
1 | # FrictionLess FormUI
2 |
3 | A simple component encapsulating a title label, FormattableTextField, validation label, and input state.
4 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/FormattableTextFieldDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormattableTextFieldDelegate.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 3/27/17.
6 | //
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol FormattableTextFieldDelegate: UITextFieldDelegate {
12 | func textFieldShouldNavigateBackwards(_ textField: FormattableTextField)
13 | func textField(_ textField: FormattableTextField, didOverflowInput string: String)
14 | func textField(_ textField: FormattableTextField, invalidInput error: Error)
15 | func editingChanged(textField: FormattableTextField)
16 | }
17 |
18 | public extension FormattableTextFieldDelegate {
19 | func textFieldShouldNavigateBackwards(_ textField: FormattableTextField) {}
20 | func textField(_ textField: FormattableTextField, didOverflowInput string: String) {}
21 | func textField(_ textField: FormattableTextField, invalidInput error: Error) {}
22 | func editingChanged(textField: FormattableTextField) {}
23 | }
24 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/Formatters/EmailFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmailFormatter.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 4/19/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum EmailFormatterError: Error {
12 | case invalidEmail
13 | }
14 |
15 | public struct EmailFormatter: TextFieldFormatter {
16 |
17 | public func validate(_ text: String) -> ValidationResult {
18 | if text.isEmail {
19 | return .valid
20 | }
21 | else {
22 | return .invalid(validationError: EmailFormatterError.invalidEmail)
23 | }
24 | }
25 |
26 | public init() {}
27 |
28 | }
29 |
30 | extension String {
31 |
32 | /// https://github.com/goktugyil/EZSwiftExtensions/blob/master/Sources/StringExtensions.swift
33 | public var isEmail: Bool {
34 | let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
35 | let firstMatch = dataDetector?.firstMatch(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange(location: 0, length: characters.count))
36 | return (firstMatch?.range.location != NSNotFound && firstMatch?.url?.scheme == "mailto")
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/Formatters/NameFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NameFormatter.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 4/19/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | public enum NameFormatterError: Error {
12 | case empty
13 | }
14 |
15 | public struct NameFormatter: TextFieldFormatter {
16 |
17 | var minimumLength = 1
18 |
19 | public func validate(_ text: String) -> ValidationResult {
20 | let length = text.characters.count
21 | if length < minimumLength {
22 | return .invalid(validationError: NameFormatterError.empty)
23 | }
24 | else {
25 | return .valid
26 | }
27 | }
28 |
29 | public init() {}
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/README.md:
--------------------------------------------------------------------------------
1 | # FormattableTextField
2 | A UX-focused textfield formatter/validator built for bulletproof as-you-type formatting.
3 |
4 | ## Features
5 | - Built for as-you-type formatting
6 | - Build your own bullet-proof formatters
7 | - Cursor position is correct through input, delete, cut, and paste
8 | - Delete across formatting characters
9 | - Support for auto-advancing for complete textfields
10 | - Support for interpreting backspace in an empty textfield as navigation to previous field
11 | - Support for overflowing input from the end of a complete textfield to the next field
12 |
13 | 
14 |
15 | 
16 |
17 | 
18 |
19 | ## Usage
20 | Create a `FormattableTextField` and assign it a `TextFieldFormatter` conforming formatter
21 | ```swift
22 | let phoneNumberTextField: FormattableTextField = {
23 | let textField = FormattableTextField()
24 | textField.formatter = PhoneFormatter()
25 | textField.keyboardType = .phonePad
26 | return textField
27 | }()
28 | ```
29 |
30 | ### FormattableTextFieldDelegate
31 | Assign the `textField.delegate` and implment any of the following methods to make experience decisions based on input.
32 | ```swift
33 | public protocol FormattableTextFieldDelegate: UITextFieldDelegate {
34 | func textFieldShouldNavigateBackwards(_ textField: FormattableTextField)
35 | func textField(_ textField: FormattableTextField, didOverflowInput string: String)
36 | func textField(_ textField: FormattableTextField, invalidInput error: Error)
37 | func editingChanged(textField: FormattableTextField)
38 | }
39 | ```
40 |
41 | ### Creating A Formatter
42 | Implement the `TextFieldFormatter` protocol to build your own formatter.
43 | ```swift
44 | // A formatter to add spaces in between numbers
45 | struct MyFormatter: TextFieldFormatter {
46 | var inputCharacterSet = CharacterSet.decimalDigits
47 | var formattingCharacterSet = CharacterSet(charactersIn: " ")
48 |
49 | func format(editingEvent: EditingEvent) -> FormattingResult {
50 | let formattedString = editingEvent.newValue.characters.map({String($0)}).joined(separator: " ")
51 | return .valid(.text(formattedString))
52 | }
53 | }
54 | ```
55 |
56 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/TextFieldFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldFormatter.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 11/21/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | public protocol TextFieldFormatter {
12 |
13 | /// A character set containing allowed input characters
14 | var inputCharacterSet: CharacterSet { get }
15 |
16 | /// A character set containing formatting characters (white space, /, etc)
17 | var formattingCharacterSet: CharacterSet { get }
18 |
19 | /// Set to `true` if deleting should erase all characters trailing the cursor.
20 | var deletingShouldRemoveTrailingCharacters: Bool { get }
21 |
22 | /**
23 | Validate text against the formatter.
24 | Returns `.valid` if the input is valid and complete.
25 |
26 | - Parameter text: The text to be validated.
27 |
28 | - Returns: A `ValidationResult` containing either `.valid` or `.invalid` with a validation error.
29 | */
30 | func validate(_ text: String) -> ValidationResult
31 |
32 | /**
33 | Format an incoming TextField `EditingEvent`. A successful format returns the formatted string and the updated cursor position.
34 |
35 | - Parameter editingEvent: A structure encapsulating the proposed edit and edit range.
36 |
37 | - Returns: A `FormattingResult` containing either
38 | - the formatted text and the updated cursor position, or
39 | - a formatting error.
40 | */
41 | func format(editingEvent: EditingEvent) -> FormattingResult
42 |
43 | /**
44 | Whether or not text satifies a "one-and-only" valid state.
45 | Used to auto-advance.
46 |
47 | Think: credit card, expiration date, CVV
48 |
49 | - Returns: A Bool indicating if the input is necessarily complete.
50 | */
51 | func isComplete(_ text: String) -> Bool
52 | }
53 |
54 | // MARK: - Protocol Defaults
55 | public extension TextFieldFormatter {
56 |
57 | var inputCharacterSet: CharacterSet {
58 | return CharacterSet().inverted
59 | }
60 |
61 | var formattingCharacterSet: CharacterSet {
62 | return CharacterSet()
63 | }
64 |
65 | var deletingShouldRemoveTrailingCharacters: Bool {
66 | return false
67 | }
68 |
69 | func validate(_ text: String) -> ValidationResult {
70 | return .valid
71 | }
72 |
73 | func format(editingEvent: EditingEvent) -> FormattingResult {
74 | return .valid(nil)
75 | }
76 |
77 | func isComplete(_ text: String) -> Bool {
78 | return false
79 | }
80 |
81 | }
82 |
83 | // MARK: - Functionality
84 | public extension TextFieldFormatter {
85 |
86 | /**
87 | Trim out formatting characters.
88 |
89 | - Returns: A string with all characters removed that aren't included in the formatter's `inputCharacterSet`
90 | */
91 | func removeFormatting(_ text: String) -> String {
92 | return text.filteringWith(characterSet: inputCharacterSet)
93 | }
94 |
95 | /**
96 | This method interprets the deletion of a single formatting character as an attempt by the user to delete the content on the other side of the formatting.
97 |
98 | This only applies for deletes where the selectedTextRange length is 0. So if a user explicitly highlights a range containing the formatting character,
99 | the leading character will remain unchanged.
100 |
101 | - Parameter editingEvent: A structure encapsulating the proposed edit and edit range.
102 |
103 | - Returns: A new `EditingEvent`, which removes in-between formatting characters, allowing for the formatter to carry out the intended edit.
104 | */
105 | func handleDeletionOfFormatting(editingEvent: EditingEvent) -> EditingEvent {
106 | var edit = editingEvent
107 | let deletesSingleFormattingCharacter = edit.isSingleCharBackspace && edit.deletesCharacterInSet(characterSet: formattingCharacterSet)
108 | if deletesSingleFormattingCharacter {
109 | edit.deleteConsecutiveCharactersInSet(characterSet: formattingCharacterSet)
110 | }
111 | return edit
112 | }
113 |
114 | /**
115 | - Returns: `true` if the input consists only of characters found in the formatter's `inputCharacterSet` or `formattingCharacterSet`
116 | */
117 | func containsValidChars(text: String?) -> Bool {
118 | let allowedSet = inputCharacterSet.union(formattingCharacterSet)
119 | let rangeOfInvalidChar = text?.rangeOfCharacter(from: allowedSet.inverted)
120 | guard rangeOfInvalidChar?.isEmpty ?? true else { return false }
121 |
122 | return true
123 | }
124 |
125 | /**
126 | If an editing event represents a delete of an input character and `deletingShouldRemoveTrailingCharacters` == true, removes all characters trailing the delete.
127 |
128 | - Returns: an adjusted `EditingEvent`
129 | */
130 | func removeCharactersTrailingDelete(textField: UITextField, editingEvent: EditingEvent) -> EditingEvent {
131 | var edit = editingEvent
132 | if editingEvent.editRange.length > 0 && editingEvent.deletesCharacterInSet(characterSet: inputCharacterSet) && deletingShouldRemoveTrailingCharacters {
133 | edit.newValue = String(edit.newValue.characters.prefix(editingEvent.newCursorPosition))
134 | }
135 | return edit
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/TextFieldFormatterDataTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldFormatterDataTypes.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 3/29/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | A structure representing a `UITextField` editing event.
13 | */
14 | public struct EditingEvent {
15 | /// The textField `text` prior to the editing event.
16 | var oldValue: String
17 |
18 | /// The `NSRange` of the text to be replaced.
19 | var editRange: NSRange
20 |
21 | /// The `NSRange` of the selected text prior to edit
22 | var selectedTextRange: NSRange?
23 |
24 | /// The new text to be replaced at `editRange`.
25 | var editString: String
26 |
27 | /// The text that would result if editString was inserted at editRange.
28 | var newValue: String
29 |
30 | /// The location of the cursor if editString was inserted at editRange.
31 | var newCursorPosition: Int
32 | }
33 |
34 | public extension EditingEvent {
35 |
36 | /// `true` if this editing event representes a deletion of text
37 | var isDelete: Bool {
38 | return editRange.length > 0 && editString.isEmpty
39 | }
40 |
41 | /// `true` if this editing event representes a backspace of exactly 1 character
42 | var isSingleCharBackspace: Bool {
43 | return editRange.length == 1 && editString.isEmpty && (selectedTextRange?.length == 0)
44 | }
45 |
46 | /**
47 | - Returns: true if edit event includes a deletion of a character in `characterSet`
48 | */
49 | func deletesCharacterInSet(characterSet: CharacterSet) -> Bool {
50 | let range = oldValue.range(fromNSRange: editRange)
51 | return oldValue.rangeOfCharacter(from: characterSet, options: NSString.CompareOptions(), range: range) != nil
52 | }
53 |
54 | /**
55 | - deletes consecutive characters of a CharacterSet preceeding the cursor.
56 | */
57 | mutating func deleteConsecutiveCharactersInSet(characterSet: CharacterSet) {
58 | // a range consisting of consecutive characters in a CharacterSet preceeding the cursor.
59 | if let deleteRange: Range = {
60 | for location in stride(from: editRange.location-1, through: 0, by: -1) {
61 | let index = newValue.index(newValue.startIndex, offsetBy: location)
62 | let oneChar = newValue.index(after: index)
63 | if oldValue.rangeOfCharacter(from: characterSet, options: NSString.CompareOptions(), range: index.. Int {
78 | if let fingerpint = newValue.fingerprint(ofCursorPosition: newCursorPosition, characterSet: characterSet), let position = text.position(ofCursorFingerprint: fingerpint) {
79 | return position
80 | }
81 | else {
82 | return text.characters.count
83 | }
84 | }
85 |
86 | }
87 |
88 | /**
89 | The result of formatting in response to an `EditingEvent`.
90 | */
91 | public enum FormattingResult {
92 | case valid(Reformatting?)
93 | case invalid(formattingError: Error)
94 | }
95 |
96 | public enum Reformatting {
97 | case text(String)
98 | case textAndCursor(String, Int)
99 | }
100 |
101 | /**
102 | The result of validating a `String` against a `TextFieldFormatter`.
103 | */
104 | public enum ValidationResult {
105 | case valid
106 | case invalid(validationError: Error)
107 | }
108 |
109 | public func == (lhs: ValidationResult, rhs: ValidationResult) -> Bool {
110 | switch (lhs, rhs) {
111 | case (.valid, .valid): return true
112 | case (.invalid(_), .invalid(_)): return true
113 | default: return false
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/Utilities/FormattableTextField+LayoutMargins.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FormattableTextField+LayoutMargins.swift
3 | // FrictionLess
4 | //
5 | // Created by Jason Clark on 7/14/17.
6 | // Copyright © 2017 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension FormattableTextField {
12 |
13 | override open func textRect(forBounds bounds: CGRect) -> CGRect {
14 | return UIEdgeInsetsInsetRect(bounds, layoutMargins)
15 | }
16 |
17 | override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
18 | return UIEdgeInsetsInsetRect(bounds, layoutMargins)
19 | }
20 |
21 | override open func editingRect(forBounds bounds: CGRect) -> CGRect {
22 | return UIEdgeInsetsInsetRect(bounds, layoutMargins)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/Utilities/RangeHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RangeHelpers.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 11/8/16.
6 | // Copyright © 2016 Raizlabs. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension String {
12 |
13 | func substring(fromNSRange range: NSRange) -> String {
14 | return substring(with: self.range(fromNSRange: range))
15 | }
16 |
17 | func range(fromNSRange range: NSRange) -> Range {
18 | return characters.index(startIndex, offsetBy: range.location).. UITextRange? {
40 | guard let selection = selection, let start = self.position(from: selection.start, offset: offset),
41 | let end = self.position(from: selection.end, offset: offset) else {
42 | return nil
43 | }
44 | return textRange(from: start, to: end)
45 | }
46 |
47 | func textRange(cursorOffset: Int) -> UITextRange? {
48 | guard let targetPosition = position(from: beginningOfDocument, offset: cursorOffset) else {
49 | return nil
50 | }
51 | return textRange(from: targetPosition, to: targetPosition)
52 | }
53 |
54 | var textRange: NSRange {
55 | return NSRange(location: 0, length: text?.characters.count ?? 0)
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/Utilities/String+CursorFingerprint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+CursorFingerprint.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 4/21/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | // Adopted from PhoneNumberKit's PhonenumberTextfield by Roy Marmelstein
12 | // For most auto-formatting edits, the cursor location can be identified and recovered by noting how many times the character to the right of the cursor appears until the end of the string.
13 |
14 | /// The "fingerprint" of the cursor position, as represented by the character following the cursor, and the number of times that character appears until the end of the sting
15 | struct CursorPositionFingerprint {
16 | let characterAfterCursor: String
17 | let repetitionCountFromEnd: Int
18 | }
19 |
20 | extension String {
21 |
22 | func fingerprint(ofCursorPosition cursorPosition: Int, characterSet: CharacterSet = CharacterSet().inverted) -> CursorPositionFingerprint? {
23 | var characterRepetitionsFromEnd = 0
24 | for i in cursorPosition ..< characters.count {
25 | let range = NSRange(location: i, length: 1)
26 | let firstCharacterAfterCursorInSet = substring(fromNSRange: range)
27 | if characterSet.contains(firstCharacterAfterCursorInSet) {
28 | for j in range.location ..< characters.count {
29 | let candidateRepeat = substring(fromNSRange: NSRange(location:j, length:1))
30 | if candidateRepeat == firstCharacterAfterCursorInSet {
31 | characterRepetitionsFromEnd += 1
32 | }
33 | }
34 | return CursorPositionFingerprint(characterAfterCursor: firstCharacterAfterCursorInSet, repetitionCountFromEnd: characterRepetitionsFromEnd)
35 | }
36 | }
37 | return nil
38 | }
39 |
40 | func position(ofCursorFingerprint cursorFingerprint: CursorPositionFingerprint) -> Int? {
41 | var countFromEnd = 0
42 |
43 | for i in stride(from: characters.count - 1, through: 0, by: -1) {
44 | let candidateRange = NSRange(location: i, length:1)
45 | let candidateCharacter = substring(fromNSRange: candidateRange)
46 | if candidateCharacter == cursorFingerprint.characterAfterCursor {
47 | countFromEnd += 1
48 | if countFromEnd == cursorFingerprint.repetitionCountFromEnd {
49 | return candidateRange.location
50 | }
51 | }
52 | }
53 | return nil
54 | }
55 |
56 | func position(ofCursorLocation oldLocation: Int, in string: String, within set: CharacterSet? = nil) -> Int {
57 | let characterSet: CharacterSet = set ?? CharacterSet().inverted
58 | if let fingerprint = fingerprint(ofCursorPosition: oldLocation, characterSet: characterSet),
59 | let position = string.position(ofCursorFingerprint: fingerprint) {
60 | return position
61 | }
62 | else {
63 | return string.characters.count
64 | }
65 | }
66 |
67 | }
68 |
69 | extension CharacterSet {
70 |
71 | func contains(_ string: String) -> Bool {
72 | return !(string.rangeOfCharacter(from: self)?.isEmpty ?? true)
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/FrictionLess/FormattableTextField/Utilities/String+Filtering.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Filtering.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 3/29/17.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /**
14 | Trim out characters not contained in `characterSet`
15 |
16 | - Parameter characterSet: The whitelist `CharacterSet` to filter the string to.
17 | - Returns: A string containing only characters in `characterSet`
18 | */
19 | func filteringWith(characterSet: CharacterSet) -> String {
20 | return components(separatedBy: characterSet.inverted).joined()
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/FrictionLess/PhoneFormatter/PhoneFormatter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PhoneFormatter.swift
3 | // Raizlabs
4 | //
5 | // Created by Jason Clark on 4/20/17.
6 | //
7 | //
8 |
9 | import Foundation
10 | import PhoneNumberKit
11 |
12 | public enum PhoneFormatterError: Error {
13 | case invalid
14 | }
15 |
16 | public struct PhoneFormatter: TextFieldFormatter {
17 |
18 | fileprivate let phoneKit = PhoneNumberKit()
19 | fileprivate let partialFormatter: PartialFormatter
20 |
21 | public init() {
22 | partialFormatter = PartialFormatter(phoneNumberKit: phoneKit)
23 | }
24 |
25 | public var inputCharacterSet: CharacterSet {
26 | let decimalSet = CharacterSet.decimalDigits
27 | let symobls = "+" //TODO if backend supported? "*#,;"
28 | let symbolSet = CharacterSet(charactersIn: symobls)
29 | return decimalSet.union(symbolSet)
30 | }
31 |
32 | public var formattingCharacterSet = CharacterSet(charactersIn: "()- ")
33 |
34 | public func format(editingEvent: EditingEvent) -> FormattingResult {
35 | let formatted = partialFormatter.formatPartial(editingEvent.newValue)
36 | return .valid(.text(formatted))
37 | }
38 |
39 | public func validate(_ text: String) -> ValidationResult {
40 | if (try? phoneKit.parse(text)) != nil {
41 | return .valid
42 | }
43 | return .invalid(validationError: PhoneFormatterError.invalid)
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/FrictionLess/PhoneFormatter/README.md:
--------------------------------------------------------------------------------
1 | # Phone Formatter
2 |
3 | An as-you-type phone formatter leveraging [Phone Number Kit](https://github.com/marmelroy/PhoneNumberKit)
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gem 'cocoapods', '~> 1.2.1'
3 |
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (2.3.5)
5 | activesupport (4.2.9)
6 | i18n (~> 0.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | addressable (2.5.1)
11 | public_suffix (~> 2.0, >= 2.0.2)
12 | babosa (1.0.2)
13 | claide (1.0.2)
14 | clamp (0.6.5)
15 | cocoapods (1.2.1)
16 | activesupport (>= 4.0.2, < 5)
17 | claide (>= 1.0.1, < 2.0)
18 | cocoapods-core (= 1.2.1)
19 | cocoapods-deintegrate (>= 1.0.1, < 2.0)
20 | cocoapods-downloader (>= 1.1.3, < 2.0)
21 | cocoapods-plugins (>= 1.0.0, < 2.0)
22 | cocoapods-search (>= 1.0.0, < 2.0)
23 | cocoapods-stats (>= 1.0.0, < 2.0)
24 | cocoapods-trunk (>= 1.2.0, < 2.0)
25 | cocoapods-try (>= 1.1.0, < 2.0)
26 | colored2 (~> 3.1)
27 | escape (~> 0.0.4)
28 | fourflusher (~> 2.0.1)
29 | gh_inspector (~> 1.0)
30 | molinillo (~> 0.5.7)
31 | nap (~> 1.0)
32 | ruby-macho (~> 1.1)
33 | xcodeproj (>= 1.4.4, < 2.0)
34 | cocoapods-core (1.2.1)
35 | activesupport (>= 4.0.2, < 5)
36 | fuzzy_match (~> 2.0.4)
37 | nap (~> 1.0)
38 | cocoapods-deintegrate (1.0.1)
39 | cocoapods-downloader (1.1.3)
40 | cocoapods-plugins (1.0.0)
41 | nap
42 | cocoapods-search (1.0.0)
43 | cocoapods-stats (1.0.0)
44 | cocoapods-trunk (1.2.0)
45 | nap (>= 0.8, < 2.0)
46 | netrc (= 0.7.8)
47 | cocoapods-try (1.1.0)
48 | colored (1.2)
49 | colored2 (3.1.2)
50 | colorize (0.8.1)
51 | commander-fastlane (4.4.5)
52 | highline (~> 1.7.2)
53 | domain_name (0.5.20170404)
54 | unf (>= 0.0.5, < 1.0.0)
55 | dotenv (2.2.1)
56 | escape (0.0.4)
57 | excon (0.57.1)
58 | faraday (0.12.2)
59 | multipart-post (>= 1.2, < 3)
60 | faraday-cookie_jar (0.0.6)
61 | faraday (>= 0.7.4)
62 | http-cookie (~> 1.0.0)
63 | faraday_middleware (0.11.0.1)
64 | faraday (>= 0.7.4, < 1.0)
65 | fastimage (2.1.0)
66 | fastlane (2.27.0)
67 | addressable (>= 2.3, < 3.0.0)
68 | babosa (>= 1.0.2, < 2.0.0)
69 | bundler (>= 1.12.0, < 2.0.0)
70 | colored
71 | commander-fastlane (>= 4.4.0, < 5.0.0)
72 | dotenv (>= 2.1.1, < 3.0.0)
73 | excon (>= 0.45.0, < 1.0.0)
74 | faraday (~> 0.9)
75 | faraday-cookie_jar (~> 0.0.6)
76 | faraday_middleware (~> 0.9)
77 | fastimage (>= 1.6)
78 | gh_inspector (>= 1.0.1, < 2.0.0)
79 | google-api-client (~> 0.9.2)
80 | highline (>= 1.7.2, < 2.0.0)
81 | json (< 3.0.0)
82 | mini_magick (~> 4.5.1)
83 | multi_json
84 | multi_xml (~> 0.5)
85 | multipart-post (~> 2.0.0)
86 | plist (>= 3.1.0, < 4.0.0)
87 | rubyzip (>= 1.1.0, < 2.0.0)
88 | security (= 0.1.3)
89 | slack-notifier (>= 1.3, < 2.0.0)
90 | terminal-notifier (>= 1.6.2, < 2.0.0)
91 | terminal-table (>= 1.4.5, < 2.0.0)
92 | tty-screen (~> 0.5.0)
93 | word_wrap (~> 1.0.0)
94 | xcodeproj (>= 1.4.4, < 2.0.0)
95 | xcpretty (>= 0.2.4, < 1.0.0)
96 | xcpretty-travis-formatter (>= 0.0.3)
97 | fourflusher (2.0.1)
98 | fuzzy_match (2.0.4)
99 | gh_inspector (1.0.3)
100 | google-api-client (0.9.28)
101 | addressable (~> 2.3)
102 | googleauth (~> 0.5)
103 | httpclient (~> 2.7)
104 | hurley (~> 0.1)
105 | memoist (~> 0.11)
106 | mime-types (>= 1.6)
107 | representable (~> 2.3.0)
108 | retriable (~> 2.0)
109 | googleauth (0.5.3)
110 | faraday (~> 0.12)
111 | jwt (~> 1.4)
112 | logging (~> 2.0)
113 | memoist (~> 0.12)
114 | multi_json (~> 1.11)
115 | os (~> 0.9)
116 | signet (~> 0.7)
117 | highline (1.7.8)
118 | http-cookie (1.0.3)
119 | domain_name (~> 0.5)
120 | httpclient (2.8.3)
121 | hurley (0.2)
122 | i18n (0.8.6)
123 | json (2.1.0)
124 | jwt (1.5.6)
125 | little-plugger (1.1.4)
126 | logging (2.2.2)
127 | little-plugger (~> 1.1)
128 | multi_json (~> 1.10)
129 | memoist (0.16.0)
130 | mime-types (3.1)
131 | mime-types-data (~> 3.2015)
132 | mime-types-data (3.2016.0521)
133 | mini_magick (4.5.1)
134 | minitest (5.10.3)
135 | molinillo (0.5.7)
136 | multi_json (1.12.1)
137 | multi_xml (0.6.0)
138 | multipart-post (2.0.0)
139 | nanaimo (0.2.3)
140 | nap (1.1.0)
141 | netrc (0.7.8)
142 | os (0.9.6)
143 | plist (3.3.0)
144 | public_suffix (2.0.5)
145 | representable (2.3.0)
146 | uber (~> 0.0.7)
147 | retriable (2.1.0)
148 | rouge (2.0.7)
149 | ruby-macho (1.1.0)
150 | rubyzip (1.2.1)
151 | security (0.1.3)
152 | signet (0.7.3)
153 | addressable (~> 2.3)
154 | faraday (~> 0.9)
155 | jwt (~> 1.5)
156 | multi_json (~> 1.10)
157 | slack-notifier (1.5.1)
158 | synx (0.2.1)
159 | clamp (~> 0.6)
160 | colorize (~> 0.7)
161 | xcodeproj (~> 1.0)
162 | terminal-notifier (1.8.0)
163 | terminal-table (1.8.0)
164 | unicode-display_width (~> 1.1, >= 1.1.1)
165 | thread_safe (0.3.6)
166 | tty-screen (0.5.0)
167 | tzinfo (1.2.3)
168 | thread_safe (~> 0.1)
169 | uber (0.0.15)
170 | unf (0.1.4)
171 | unf_ext
172 | unf_ext (0.0.7.4)
173 | unicode-display_width (1.3.0)
174 | word_wrap (1.0.0)
175 | xcodeproj (1.5.1)
176 | CFPropertyList (~> 2.3.3)
177 | claide (>= 1.0.2, < 2.0)
178 | colored2 (~> 3.1)
179 | nanaimo (~> 0.2.3)
180 | xcpretty (0.2.8)
181 | rouge (~> 2.0.7)
182 | xcpretty-travis-formatter (0.0.4)
183 | xcpretty (~> 0.2, >= 0.0.7)
184 |
185 | PLATFORMS
186 | ruby
187 |
188 | DEPENDENCIES
189 | cocoapods (~> 1.2.1)
190 | fastlane (~> 2.27.0)
191 | nanaimo (~> 0.2.2)
192 | synx (~> 0.2.1)
193 |
194 | BUNDLED WITH
195 | 1.15.3
196 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 jason.clark@raizlabs.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FrictionLess
2 | [](https://circleci.com/gh/Raizlabs/FrictionLess)
3 | [](http://cocoapods.org/pods/FrictionLess)
4 | [](http://cocoapods.org/pods/FrictionLess)
5 | [](http://cocoapods.org/pods/FrictionLess)
6 | [](https://github.com/Carthage/Carthage)
7 |
8 | A collection of UX-focused swift components for reducing friction in "user work".
9 | ## Components
10 | # [Card Entry](FrictionLess/CardEntry/)
11 | A drop-in, customizable card entry widget.
12 |
13 | 
14 |
15 | # [FormattableTextField](FrictionLess/FormattableTextField/)
16 | A UX-focused textfield formatter/validator built for as-you-type formatting.
17 |
18 | 
19 |
20 | 
21 |
22 | ## Example
23 |
24 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
25 |
26 | ## Installation
27 |
28 | FrictionLess is available through [CocoaPods](http://cocoapods.org). To install
29 | it, simply add the following line to your Podfile:
30 |
31 | ```ruby
32 | pod "FrictionLess"
33 | ```
34 |
35 | ## Author
36 |
37 | jason.clark@raizlabs.com
38 |
39 | ## License
40 |
41 | FrictionLess is available under the MIT license. See the LICENSE file for more info.
42 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | xcode:
3 | version: "8.3.2"
4 |
5 | dependencies:
6 | pre:
7 | - brew update 1> /dev/null 2> /dev/null
8 | - brew outdated carthage || brew upgrade carthage
9 | - gem install bundler
10 |
11 | test:
12 | override:
13 | - ./scripts/iOS.sh
14 | post:
15 | - carthage build --no-skip-current && for platform in iOS; do test -d Carthage/Build/${platform}/FrictionLess.framework || exit 1; done
16 | - bundle exec pod lib lint
17 |
18 |
--------------------------------------------------------------------------------
/scripts/iOS.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -o pipefail && \
4 | xcodebuild clean build test \
5 | -workspace Example/FrictionLess.xcworkspace \
6 | -scheme FrictionLess-Example \
7 | -sdk iphonesimulator \
8 | -destination "platform=iOS Simulator,name=iPhone 6S,OS=10.3" \
9 | CODE_SIGNING_REQUIRED=NO \
10 | CODE_SIGN_IDENTITY= \
11 | | xcpretty
12 |
--------------------------------------------------------------------------------