├── .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 | [![Swift 3.0](https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat)](https://swift.org) 4 | [![CircleCI](https://img.shields.io/circleci/project/github/Raizlabs/Anchorage.svg)]() 5 | [![Version](https://img.shields.io/cocoapods/v/Anchorage.svg?style=flat)](https://cocoadocs.org/docsets/Anchorage) 6 | [![Platform](https://img.shields.io/cocoapods/p/Anchorage.svg?style=flat)](http://cocoapods.org/pods/Anchorage) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | ![PhoneNumberKit](https://cloud.githubusercontent.com/assets/889949/20864386/a1307950-b9ef-11e6-8a58-e9c5103738e7.png) 2 | [![Platform](https://img.shields.io/cocoapods/p/PhoneNumberKit.svg?maxAge=2592000)](http://cocoapods.org/?q=PhoneNumberKit) 3 | [![Build Status](https://travis-ci.org/marmelroy/PhoneNumberKit.svg?branch=master)](https://travis-ci.org/marmelroy/PhoneNumberKit) [![Version](http://img.shields.io/cocoapods/v/PhoneNumberKit.svg)](http://cocoapods.org/?q=PhoneNumberKit) 4 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | ![AsYouTypeFormatter](http://i.giphy.com/3o6gbgrudyCM8Ak6yc.gif) 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 | ![Card Entry Demo](../../Documentation/CardEntryDemo.gif) 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 | ![PhoneFormatter Demo](../../Documentation/PhoneFormatter.gif) 14 | 15 | ![EmailFormatter Demo](../../Documentation/EmailFormatter.gif) 16 | 17 | ![Card Entry Demo](../../Documentation/CardEntryDemo.gif) 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 | ![PhoneFormatter Demo](../../Documentation/PhoneFormatter.gif) 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 | [![CircleCI](https://circleci.com/gh/Raizlabs/FrictionLess.svg?style=svg)](https://circleci.com/gh/Raizlabs/FrictionLess) 3 | [![Version](https://img.shields.io/cocoapods/v/FrictionLess.svg?style=flat)](http://cocoapods.org/pods/FrictionLess) 4 | [![License](https://img.shields.io/cocoapods/l/FrictionLess.svg?style=flat)](http://cocoapods.org/pods/FrictionLess) 5 | [![Platform](https://img.shields.io/cocoapods/p/FrictionLess.svg?style=flat)](http://cocoapods.org/pods/FrictionLess) 6 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | ![Card Entry Demo](Documentation/CardEntryDemo.gif) 14 | 15 | # [FormattableTextField](FrictionLess/FormattableTextField/) 16 | A UX-focused textfield formatter/validator built for as-you-type formatting. 17 | 18 | ![PhoneFormatter Demo](Documentation/PhoneFormatter.gif) 19 | 20 | ![EmailFormatter Demo](Documentation/EmailFormatter.gif) 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 | --------------------------------------------------------------------------------