├── .gitignore ├── .swiftlint.yml ├── .travis.yml ├── Example ├── .gitignore ├── .swiftlint.yml ├── Gemfile ├── Makefile ├── OTPTextFieldExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── OTPTextFieldExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── OTPTextFieldExample │ ├── Application │ │ ├── AppDelegate.swift │ │ ├── DeepLinkOption.swift │ │ ├── Info.plist │ │ └── LaunchScreen.xib │ ├── Flows │ │ └── Main │ │ │ ├── CustomOtpField │ │ │ ├── Configurator │ │ │ │ └── CustomOtpFieldModuleConfigurator.swift │ │ │ ├── Presenter │ │ │ │ ├── CustomOtpFieldModuleInput.swift │ │ │ │ ├── CustomOtpFieldModuleOutput.swift │ │ │ │ └── CustomOtpFieldPresenter.swift │ │ │ └── View │ │ │ │ ├── CustomOTPField │ │ │ │ ├── CustomFieldAdapter.swift │ │ │ │ └── CustomPinView │ │ │ │ │ ├── CustomPinView.swift │ │ │ │ │ └── CustomPinView.xib │ │ │ │ ├── CustomOtpFieldViewController.swift │ │ │ │ ├── CustomOtpFieldViewController.xib │ │ │ │ ├── CustomOtpFieldViewInput.swift │ │ │ │ └── CustomOtpFieldViewOutput.swift │ │ │ ├── DefaultOtpField │ │ │ ├── Configurator │ │ │ │ └── DefaultOtpFieldModuleConfigurator.swift │ │ │ ├── Presenter │ │ │ │ ├── DefaultOtpFieldModuleInput.swift │ │ │ │ ├── DefaultOtpFieldModuleOutput.swift │ │ │ │ └── DefaultOtpFieldPresenter.swift │ │ │ └── View │ │ │ │ ├── DefaultOtpFieldViewController.swift │ │ │ │ ├── DefaultOtpFieldViewController.xib │ │ │ │ ├── DefaultOtpFieldViewInput.swift │ │ │ │ └── DefaultOtpFieldViewOutput.swift │ │ │ ├── Main │ │ │ ├── Configurator │ │ │ │ └── MainModuleConfigurator.swift │ │ │ ├── Presenter │ │ │ │ ├── MainModuleInput.swift │ │ │ │ ├── MainModuleOutput.swift │ │ │ │ └── MainPresenter.swift │ │ │ └── View │ │ │ │ ├── Cells │ │ │ │ ├── MainFeedCell.swift │ │ │ │ └── MainFeedCell.xib │ │ │ │ ├── MainViewController.swift │ │ │ │ ├── MainViewController.xib │ │ │ │ ├── MainViewInput.swift │ │ │ │ └── MainViewOutput.swift │ │ │ ├── MainCoordinator.swift │ │ │ ├── MainCoordinatorOutput.swift │ │ │ ├── PlainOtpField │ │ │ ├── Configurator │ │ │ │ └── PlainOtpFieldModuleConfigurator.swift │ │ │ ├── Presenter │ │ │ │ ├── PlainOtpFieldModuleInput.swift │ │ │ │ ├── PlainOtpFieldModuleOutput.swift │ │ │ │ └── PlainOtpFieldPresenter.swift │ │ │ └── View │ │ │ │ ├── PlainOTPField │ │ │ │ ├── PlainOTPFieldAdapter.swift │ │ │ │ └── PlainPinView │ │ │ │ │ ├── PlainPinView.swift │ │ │ │ │ └── PlainPinView.xib │ │ │ │ ├── PlainOtpFieldViewController.swift │ │ │ │ ├── PlainOtpFieldViewController.xib │ │ │ │ ├── PlainOtpFieldViewInput.swift │ │ │ │ └── PlainOtpFieldViewOutput.swift │ │ │ └── RoundOtpField │ │ │ ├── Configurator │ │ │ └── RoundOtpFieldModuleConfigurator.swift │ │ │ ├── Presenter │ │ │ ├── RoundOtpFieldModuleInput.swift │ │ │ ├── RoundOtpFieldModuleOutput.swift │ │ │ └── RoundOtpFieldPresenter.swift │ │ │ └── View │ │ │ ├── RoundOtpFieldViewController.swift │ │ │ ├── RoundOtpFieldViewController.xib │ │ │ ├── RoundOtpFieldViewInput.swift │ │ │ └── RoundOtpFieldViewOutput.swift │ ├── Library │ │ ├── Base Classes │ │ │ ├── BaseCoordinator.swift │ │ │ └── DefaultNavigationController.swift │ │ ├── Enums │ │ │ └── OTPFieldType.swift │ │ ├── Extensions │ │ │ └── UIKit │ │ │ │ ├── NavigationBar.swift │ │ │ │ └── UIApplication.swift │ │ ├── Protocols │ │ │ ├── .gitkeep │ │ │ ├── Coordinator.swift │ │ │ ├── Presentable.swift │ │ │ └── Router.swift │ │ ├── Reusable Layer │ │ │ └── .gitkeep │ │ ├── Routers │ │ │ ├── .gitkeep │ │ │ └── MainRouter.swift │ │ └── Utils │ │ │ └── .gitkeep │ └── Resources │ │ ├── Colors │ │ └── Colors.swift │ │ ├── Constants │ │ └── .gitkeep │ │ ├── Fonts │ │ ├── .gitkeep │ │ └── Fonts.swift │ │ ├── Images │ │ ├── Assets.swift │ │ └── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ │ ├── Colors │ │ │ ├── Contents.json │ │ │ ├── TextColor.colorset │ │ │ │ └── Contents.json │ │ │ ├── background.colorset │ │ │ │ └── Contents.json │ │ │ ├── contaienrBackground.colorset │ │ │ │ └── Contents.json │ │ │ ├── defaultBlue.colorset │ │ │ │ └── Contents.json │ │ │ └── red.colorset │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── icon-arrow-right.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-arrow-right.png │ │ │ ├── icon-arrow-right@2x.png │ │ │ └── icon-arrow-right@3x.png │ │ │ └── icon-back-left.imageset │ │ │ ├── Contents.json │ │ │ ├── icon-back-left.png │ │ │ ├── icon-back-left@2x.png │ │ │ └── icon-back-left@3x.png │ │ └── Strings │ │ ├── Localizable.strings │ │ └── Strings.swift ├── OTPTextFieldExampleTests │ └── Tests │ │ └── Flows │ │ └── Main │ │ └── Main │ │ └── Configurator │ │ └── MainModuleConfiguratorTests.swift ├── Podfile ├── README.md ├── Rambafile ├── ci │ ├── JenkinsfilePullRequestJob.groovy │ └── JenkinsfileTagJob.groovy ├── commit-msg ├── fastlane │ ├── Appfile │ ├── Fastfile │ ├── Pluginfile │ └── README.md └── swiftgen.yml ├── Gemfile ├── Images └── exampleVideo.gif ├── LICENSE ├── Makefile ├── OTPTextField.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── OTPTextField.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── OTPTextField ├── Info.plist ├── Library │ ├── BaseClasses │ │ └── BaseInputView.swift │ ├── Extensions │ │ ├── Array.swift │ │ └── UIView.swift │ └── Protocols │ │ ├── OTPTextFieldData.swift │ │ └── PinContainer.swift ├── OTPTextField.h └── TextField │ ├── Configuration │ └── OTPFieldConfiguration.swift │ ├── Default │ ├── Adapter │ │ ├── DefaultTextFieldAdapter │ │ │ └── DefaultTextFieldAdapter.swift │ │ └── RoundTextFieldAdapter │ │ │ └── RoundTextFieldAdapter.swift │ └── PinViews │ │ ├── DefaultPinView │ │ ├── DefaultPinView.swift │ │ └── DefaultPinView.xib │ │ └── RoundPinView │ │ ├── RoundPinView.swift │ │ └── RoundPinView.xib │ └── OTPTextField.swift ├── OTPTextFieldTests ├── Info.plist └── OTPTextFieldTests.swift ├── Podfile ├── README.md ├── SFOTPTextField.podspec └── _config.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData/ 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *.gcno 20 | *.xcscmblueprint 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | Podfile.lock 29 | Pods/ 30 | 31 | # Exported environment variables for XcodeCoverage 32 | env.sh 33 | 34 | # OS X Finder stuff 35 | .DS_Store 36 | 37 | # Fastlane Stuff 38 | fastlane/local_config.sh 39 | fastlane/test_output 40 | fastlane/Provisioning 41 | fastlane/Build 42 | fastlane/report.xml 43 | buildData 44 | 45 | # Bundler 46 | .bundle/ 47 | Gemfile.lock 48 | 49 | # Generamba 50 | Templates/ 51 | 52 | # JSCPD 53 | report/ -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | whitelist_rules: 2 | - attributes 3 | - class_delegate_protocol 4 | - closing_brace 5 | - closure_end_indentation 6 | - closure_spacing 7 | - colon 8 | - comma 9 | - cyclomatic_complexity 10 | - empty_count 11 | - empty_parameters 12 | - empty_parentheses_with_trailing_closure 13 | - explicit_init 14 | - first_where 15 | - force_cast 16 | - force_try 17 | - force_unwrapping 18 | - implicitly_unwrapped_optional 19 | - implicit_getter 20 | - leading_whitespace 21 | - legacy_cggeometry_functions 22 | - legacy_constant 23 | - legacy_constructor 24 | - legacy_nsgeometry_functions 25 | - opening_brace 26 | - operator_usage_whitespace 27 | - redundant_discardable_let 28 | - redundant_nil_coalescing 29 | - redundant_void_return 30 | - return_arrow_whitespace 31 | - shorthand_operator 32 | - statement_position 33 | - syntactic_sugar 34 | - trailing_comma 35 | - trailing_newline 36 | - trailing_semicolon 37 | - trailing_whitespace 38 | - vertical_whitespace 39 | - void_return 40 | - weak_delegate 41 | - custom_rules 42 | - todo 43 | - closure_parameter_position 44 | - collection_alignment 45 | - conditional_returns_on_newline 46 | - control_statement 47 | - convenience_type 48 | - discouraged_optional_boolean 49 | - empty_string 50 | - function_parameter_count 51 | - inert_defer 52 | - mark 53 | - toggle_bool 54 | - trailing_closure 55 | - unused_import 56 | - unused_optional_binding 57 | 58 | # - missing_docs 59 | 60 | # logic 61 | # disabled_rules: # rule identifiers to exclude from running 62 | # - leading_whitespace 63 | # - line_length 64 | # - variable_name 65 | # - file_length 66 | # - missing_docs 67 | 68 | # - force_cast 69 | # - force_try 70 | # - force_unwrapping 71 | 72 | # # 73 | opt_in_rules: # some rules are only opt-in 74 | # - opening_brace 75 | # - closure_spacing 76 | # - colon 77 | # - comma 78 | # - conditional_returns_on_newline 79 | # - control_statement 80 | # - legacy_cggeometry_functions 81 | # - legacy_constant 82 | # - legacy_constructor 83 | # - mark 84 | # - overridden_super_call 85 | # - return_arrow_whitespace 86 | # - statement_position 87 | # - trailing_newline 88 | # - trailing_semicolon 89 | # - trailing_whitespace 90 | # - vertical_whitespace 91 | excluded: # paths to ignore during linting. Takes precedence over `included`. 92 | - fastlane 93 | - Pods 94 | - .bundle 95 | - Example 96 | 97 | custom_rules: 98 | realm_in_ui: 99 | included: "Screens/.*.swift|User Stories/.*.swift" 100 | name: "Realm can be used only in services" 101 | regex: "Realm" 102 | message: "Realm can be used only in serivces" 103 | severity: error 104 | 105 | disclosure_of_view_details: 106 | included: ".*ViewOutput.swift|.*ViewInput.swift" 107 | name: "Details opening in View protocols" 108 | regex: "cell|Cell|button|Button|Table|tableView" 109 | message: "The disclosure of details the implementation should be avoided" 110 | severity: error 111 | 112 | view_protocol_error: 113 | included: ".*ViewOutput.swift|.*ViewInput.swift" 114 | name: "Property in view protocol" 115 | regex: " var " 116 | message: "View protocol should contains only methods" 117 | severity: error 118 | 119 | router_protocol_error: 120 | included: ".*RouterInput.swift" 121 | name: "View in Router protocol" 122 | regex: "view|View" 123 | message: "Router protocol methods should contains `Module` instead `View`" 124 | severity: error 125 | 126 | open_iboutlets: 127 | included: ".*.swift" 128 | name: "IBOutlet opening" 129 | regex: "@IBOutlet ?(weak){0,1} var" 130 | message: "IBOutlet should be private or fileprivate" 131 | severity: error 132 | 133 | # configurable rules can be customized from this configuration file 134 | # binary rules can set their severity level 135 | # force_cast: warning # implicitly 136 | # force_try: 137 | # severity: warning # explicitly 138 | # rules that have both warning and error levels, can set just the warning level 139 | # implicitly 140 | # line_length: 110 141 | # # they can set both implicitly with an array 142 | # type_body_length: 143 | # - 300 # warning 144 | # - 400 # error 145 | # # or they can set both explicitly 146 | # file_length: 147 | # warning: 500 148 | # error: 1200 149 | # # naming rules can set warnings/errors for min_length and max_length 150 | # # additionally they can set excluded names 151 | # type_name: 152 | # min_length: 4 # only warning 153 | # max_length: # warning and error 154 | # warning: 40 155 | # error: 50 156 | # excluded: iPhone # excluded via string 157 | # variable_name: 158 | # min_length: # only min_length 159 | # error: 4 # only error 160 | # excluded: # excluded via string array 161 | # - id 162 | # - URL 163 | # - GlobalAPIKey 164 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: Swift 2 | osx_image: xcode11.3 3 | xcode_project: OTPTextField.xcodeproj 4 | xcode_scheme: OTPTextField 5 | before_install: 6 | - gem install xcpretty -N 7 | - make init 8 | script: 9 | - set -o pipefail 10 | - xcodebuild clean build -sdk iphonesimulator -workspace OTPTextField.xcworkspace -scheme OTPTextField CODE_SIGNING_REQUIRED=NO | xcpretty -c 11 | -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData/ 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *.gcno 20 | *.xcscmblueprint 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | Podfile.lock 29 | Pods/ 30 | 31 | # Exported environment variables for XcodeCoverage 32 | env.sh 33 | 34 | # OS X Finder stuff 35 | .DS_Store 36 | 37 | # Fastlane Stuff 38 | fastlane/local_config.sh 39 | fastlane/test_output 40 | fastlane/Provisioning 41 | fastlane/Build 42 | fastlane/report.xml 43 | buildData 44 | 45 | # Bundler 46 | .bundle/ 47 | Gemfile.lock 48 | 49 | # Generamba 50 | Templates/ -------------------------------------------------------------------------------- /Example/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | whitelist_rules: 2 | - attributes 3 | - class_delegate_protocol 4 | - closing_brace 5 | - closure_end_indentation 6 | - closure_parameter_position 7 | - closure_spacing 8 | - collection_alignment 9 | - colon 10 | - comma 11 | - conditional_returns_on_newline 12 | - control_statement 13 | - convenience_type 14 | - custom_rules 15 | - cyclomatic_complexity 16 | - discouraged_optional_boolean 17 | - duplicate_imports 18 | - empty_count 19 | - empty_parameters 20 | - empty_parentheses_with_trailing_closure 21 | - empty_string 22 | - explicit_init 23 | - file_length 24 | - first_where 25 | - force_cast 26 | - force_try 27 | - force_unwrapping 28 | - function_parameter_count 29 | - implicit_getter 30 | - implicitly_unwrapped_optional 31 | - inert_defer 32 | - large_tuple 33 | - last_where 34 | - leading_whitespace 35 | - legacy_cggeometry_functions 36 | - legacy_constant 37 | - legacy_constructor 38 | - legacy_hashing 39 | - legacy_nsgeometry_functions 40 | - line_length 41 | - literal_expression_end_indentation 42 | - mark 43 | - multiline_arguments 44 | - multiline_literal_brackets 45 | - notification_center_detachment 46 | - opening_brace 47 | - operator_usage_whitespace 48 | - redundant_discardable_let 49 | - redundant_optional_initialization 50 | - redundant_nil_coalescing 51 | - redundant_void_return 52 | - return_arrow_whitespace 53 | - shorthand_operator 54 | - statement_position 55 | - syntactic_sugar 56 | - todo 57 | - toggle_bool 58 | - trailing_comma 59 | - trailing_newline 60 | - trailing_semicolon 61 | - trailing_whitespace 62 | - unused_import 63 | - unused_optional_binding 64 | - unused_setter_value 65 | - vertical_whitespace 66 | - void_return 67 | - weak_delegate 68 | 69 | disabled_rules: # rule identifiers to exclude from running 70 | 71 | opt_in_rules: # some rules are only opt-in 72 | 73 | excluded: # paths to ignore during linting. Takes precedence over `included`. 74 | - fastlane 75 | - Pods 76 | - .bundle 77 | 78 | custom_rules: 79 | image_name_initialization: # Disable UIImage init from name 80 | included: ".*.swift" 81 | name: "Image initialization" 82 | regex: 'UIImage\(named:[^)]+\)' 83 | message: "Use UIImage(assetName: ) instead" 84 | severity: error 85 | 86 | realm_in_ui: 87 | included: "Screens/.*.swift|Flows/.*.swift|User Stories/.*.swift" 88 | name: "Realm can be used only in services" 89 | regex: "Realm" 90 | message: "Realm can be used only in serivces" 91 | severity: error 92 | 93 | disclosure_of_view_details: 94 | included: ".*ViewOutput.swift|.*ViewInput.swift" 95 | name: "Details opening in View protocols" 96 | regex: "cell|Cell|button|Button|Table|tableView" 97 | message: "The disclosure of details the implementation should be avoided" 98 | severity: error 99 | 100 | view_protocol_error: 101 | included: ".*ViewOutput.swift|.*ViewInput.swift" 102 | name: "Property in view protocol" 103 | regex: " var " 104 | message: "View protocol should contains only methods" 105 | severity: error 106 | 107 | open_iboutlets: 108 | included: ".*.swift" 109 | name: "IBOutlet opening" 110 | regex: "@IBOutlet ?(weak){0,1} var" 111 | message: "IBOutlet should be private or fileprivate" 112 | severity: error 113 | 114 | open_ibaction: 115 | included: ".*.swift" 116 | name: "IBAction opening" 117 | regex: "@IBAction func" 118 | message: "IBAction should be private or fileprivate" 119 | severity: error 120 | 121 | mark_newlines: 122 | included: ".*.swift" 123 | name: "MARK newlines surrounding" 124 | regex: '(([}{)\w \t]+\n{1}[ \t]*)(\/\/ MARK: - [\w ]*))|((\/\/ MARK: - [\w ]*)(\n{1}[ \t]*\w+))' 125 | message: "Every MARK should be surrounded with 1 newline before and 1 after it" 126 | severity: warning 127 | 128 | line_length: 300 129 | 130 | file_length: 131 | warning: 500 132 | error: 1200 133 | 134 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit) 135 | -------------------------------------------------------------------------------- /Example/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Ensure github repositories are fetched using HTTPS 4 | git_source(:github) do |repo_name| 5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 6 | puts(repo_name) 7 | "https://github.com/#{repo_name}.git" 8 | end if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('2') 9 | 10 | gem 'cocoapods', "1.7.3" 11 | gem 'synx', "~> 0.2.1" 12 | gem 'xcpretty', "0.3.0" 13 | gem 'generamba', github: 'surfstudio/Generamba', branch: 'develop', :ref => '91957270f4bc0092305ce6dbf016be5259720d33' 14 | 15 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 16 | eval_gemfile(plugins_path) if File.exist?(plugins_path) -------------------------------------------------------------------------------- /Example/Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | # Install bundler if not installed 3 | if ! gem spec bundler > /dev/null 2>&1; then\ 4 | echo "bundler gem is not installed! Run gem install bundler to install it";\ 5 | -sudo gem install bundler -v "1.17.3";\ 6 | fi 7 | -bundle install --path .bundle 8 | -bundle exec pod repo update 9 | -bundle exec pod install 10 | -bundle exec generamba template install 11 | 12 | # Install git hooks 13 | mkdir -p .git/hooks 14 | chmod +x commit-msg 15 | ln -s -f ../../commit-msg .git/hooks/commit-msg 16 | 17 | ## Used to build target. Usually, it is not called manually, it is necessary for the CI to work. 18 | build: 19 | bundle exec fastlane build clean:true 20 | 21 | ## Used to create a new coordinator with folder for a new flow. Example: make coordinator modName= 22 | coordinator: 23 | bundle exec generamba gen $(modName) surf_mvp_coordinator 24 | 25 | ## Used to create a new module. Example: make screen modName= flow= 26 | screen: 27 | bundle exec generamba gen $(modName) surf_mvp_coordinatable_module --module_path 'OTPTextFieldExample/Flows/$(flow)' 28 | 29 | ## Used to create a new alert. Example: make alert modName= flow= 30 | alert: 31 | bundle exec generamba gen $(modName) surf_mvp_coordinatable_alert --module_path 'OTPTextFieldExample/Flows/$(flow)' 32 | 33 | ## Run tests 34 | test: 35 | bundle exec fastlane tests 36 | 37 | ## Convert groups to folders if needed and sort with alphabetic order 38 | synx: 39 | bundle exec synx --exclusion "OTPTextFieldExample/Non-iOS Resources" OTPTextFieldExample.xcodeproj 40 | 41 | ## Allows you to perfrom swiftlint autocorrect command. 42 | format: 43 | ./Pods/SwiftLint/swiftlint autocorrect --config .swiftlint.yml 44 | 45 | ## Run SwiftLint check 46 | lint: 47 | ./Pods/SwiftLint/swiftlint lint --config .swiftlint.yml 48 | 49 | ## Execute pod install command 50 | pod: 51 | -bundle exec pod install 52 | 53 | ## Default configuration for beta command 54 | config ?= debug 55 | 56 | ## Create and uploads build to Fabric or AppStoreConnect 57 | ## - Parameter config: Configuration for build. 58 | ## - Allowed inputs: 59 | ## config=debug - sends debug build to Fabric 60 | ## config=rc - sends release build to AppStoreConnect 61 | beta: 62 | ifeq ($(config),debug) 63 | bundle exec fastlane beta destination:$(config) 64 | else ifeq ($(config),rc) 65 | bundle exec fastlane release 66 | else 67 | @echo "No suitable command for" $(config) 68 | exit 1; 69 | endif 70 | 71 | ## Prepare application for beta release - increment build numbers and set tags. 72 | ## Pass skip_push=true if you need to skip push to git 73 | prepare_for_beta: 74 | bundle exec fastlane prepare_for_beta type:debug skip_push:$(skip_push) 75 | 76 | ## Prepare application for release - set rc tag. 77 | ## Pass include_beta=true if you need to call prepare_for_beta to make test and prod build too 78 | ## Pass skip_push=true if you need to skip push to git 79 | prepare_for_release: 80 | bundle exec fastlane prepare_for_release include_beta:$(include_beta) skip_push:$(skip_push) 81 | 82 | # COLORS 83 | GREEN := $(shell tput -Txterm setaf 2) 84 | YELLOW := $(shell tput -Txterm setaf 3) 85 | WHITE := $(shell tput -Txterm setaf 7) 86 | RESET := $(shell tput -Txterm sgr0) 87 | 88 | 89 | TARGET_MAX_CHAR_NUM=20 90 | ## Show help 91 | help: 92 | @echo '' 93 | @echo 'Usage:' 94 | @echo ' ${YELLOW}make${RESET} ${GREEN}${RESET}' 95 | @echo '' 96 | @echo 'Targets:' 97 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 98 | helpMessage = match(lastLine, /^## (.*)/); \ 99 | if (helpMessage) { \ 100 | helpCommand = substr($$1, 0, index($$1, ":")-1); \ 101 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ 102 | printf " ${YELLOW}%-$(TARGET_MAX_CHAR_NUM)s${RESET} ${GREEN}%s${RESET}\n", helpCommand, helpMessage; \ 103 | } \ 104 | } \ 105 | { lastLine = $$0 }' $(MAKEFILE_LIST) 106 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | @UIApplicationMain 8 | final class AppDelegate: UIResponder, UIApplicationDelegate { 9 | 10 | // MARK: - Properties 11 | 12 | var window: UIWindow? 13 | 14 | // MARK: - Private Properties 15 | 16 | private lazy var mainCoordinator: Coordinator = makeCoordinator() 17 | 18 | // MARK: - UIApplicationDelegate 19 | 20 | func application(_ application: UIApplication, 21 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 22 | initializeRootView() 23 | mainCoordinator.start() 24 | return true 25 | } 26 | 27 | } 28 | 29 | // MARK: - Private Methods 30 | 31 | private extension AppDelegate { 32 | 33 | func initializeRootView() { 34 | window = UIWindow(frame: UIScreen.main.bounds) 35 | window?.rootViewController = UIViewController() 36 | window?.makeKeyAndVisible() 37 | } 38 | 39 | func makeCoordinator() -> Coordinator { 40 | return MainCoordinator() 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Application/DeepLinkOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | enum DeepLinkOption { 8 | } 9 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Application/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UIUserInterfaceStyle 36 | Light 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Application/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/Configurator/CustomOtpFieldModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldModuleConfigurator.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CustomOtpFieldModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, CustomOtpFieldModuleOutput) { 14 | let view = CustomOtpFieldViewController() 15 | let presenter = CustomOtpFieldPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/Presenter/CustomOtpFieldModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldModuleInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol CustomOtpFieldModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/Presenter/CustomOtpFieldModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldModuleOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol CustomOtpFieldModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/Presenter/CustomOtpFieldPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldPresenter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | final class CustomOtpFieldPresenter: CustomOtpFieldModuleOutput { 10 | 11 | // MARK: - CustomOtpFieldModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: CustomOtpFieldViewInput? 16 | 17 | } 18 | 19 | // MARK: - CustomOtpFieldModuleInput 20 | 21 | extension CustomOtpFieldPresenter: CustomOtpFieldModuleInput { 22 | } 23 | 24 | // MARK: - CustomOtpFieldViewOutput 25 | 26 | extension CustomOtpFieldPresenter: CustomOtpFieldViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOTPField/CustomFieldAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomFieldAdapter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 20.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | import SurfUtils 12 | 13 | final class CustomFieldAdapter: NSObject { 14 | 15 | // MARK: - Constants 16 | 17 | private enum Constants { 18 | static let numberOfPins = 4 19 | static let pinSize = CGSize(width: 40, height: 62) 20 | static let space: CGFloat = 10 21 | } 22 | 23 | } 24 | 25 | // MARK: - OTPTextFieldData 26 | 27 | extension CustomFieldAdapter: OTPTextFieldData { 28 | 29 | public func numberOfPins() -> Int { 30 | return Constants.numberOfPins 31 | } 32 | 33 | public func otpTextField(viewAt index: Int) -> PinContainer { 34 | guard let pinView = CustomPinView.loadFromNib() as? PinContainer else { 35 | fatalError("Can't find class for init pinField") 36 | } 37 | return pinView 38 | } 39 | 40 | public func otpTextField(sizeForViewAt index: Int) -> CGSize { 41 | return Constants.pinSize 42 | } 43 | 44 | public func otpTextField(spaceForViewAt index: Int) -> CGFloat { 45 | return Constants.space 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOTPField/CustomPinView/CustomPinView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPinView.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 20.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class CustomPinView: UIView { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var indicatorView: UIView! 17 | @IBOutlet private weak var codeLabel: UILabel! 18 | 19 | // MARK: - UIView 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | setupInitialState() 24 | } 25 | 26 | } 27 | 28 | // MARK: - PinContainer 29 | 30 | extension CustomPinView: PinContainer { 31 | 32 | func set(value: String?) { 33 | codeLabel.text = value 34 | } 35 | 36 | func clear() { 37 | codeLabel.text = nil 38 | } 39 | 40 | func setupState(isActive: Bool, isError: Bool) { 41 | if isError { 42 | indicatorView.backgroundColor = Colors.Figma.defaultRed 43 | } else { 44 | let color = isActive ? Colors.Figma.defaultBlue : Colors.Figma.negativeBackground 45 | indicatorView.backgroundColor = color 46 | } 47 | } 48 | 49 | } 50 | 51 | // MARK: – Configuration 52 | 53 | private extension CustomPinView { 54 | 55 | func setupInitialState() { 56 | configureContainer() 57 | configureIndicator() 58 | configureCodeLabel() 59 | } 60 | 61 | func configureContainer() { 62 | backgroundColor = .clear 63 | } 64 | 65 | func configureIndicator() { 66 | indicatorView.backgroundColor = Colors.Figma.negativeBackground 67 | } 68 | 69 | func configureCodeLabel() { 70 | codeLabel.font = UIFont.systemFont(ofSize: 50, weight: .light) 71 | codeLabel.textColor = Colors.Figma.defaultText 72 | codeLabel.text = nil 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOTPField/CustomPinView/CustomPinView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOtpFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldViewController.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class CustomOtpFieldViewController: UIViewController { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var descriptionLabel: UILabel! 17 | @IBOutlet private weak var otpField: OTPTextField! 18 | 19 | // MARK: - Properties 20 | 21 | var output: CustomOtpFieldViewOutput? 22 | 23 | // MARK: - UIViewController 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | output?.viewLoaded() 28 | } 29 | 30 | } 31 | 32 | // MARK: - CustomOtpFieldViewInput 33 | 34 | extension CustomOtpFieldViewController: CustomOtpFieldViewInput { 35 | 36 | func setupInitialState() { 37 | configureNavigationBar() 38 | configureDescription() 39 | configureOtpField() 40 | } 41 | 42 | } 43 | 44 | // MARK: - Configuration 45 | 46 | private extension CustomOtpFieldViewController { 47 | 48 | func configureNavigationBar() { 49 | title = OTPFieldType.custom.title 50 | } 51 | 52 | func configureDescription() { 53 | descriptionLabel.font = UIFont.systemFont(ofSize: 15, weight: .regular) 54 | descriptionLabel.textColor = Colors.Figma.defaultText 55 | descriptionLabel.text = OTPFieldType.custom.description 56 | descriptionLabel.numberOfLines = 0 57 | } 58 | 59 | func configureOtpField() { 60 | let configuration = OTPFieldConfiguration(adapter: CustomFieldAdapter()) 61 | otpField.setConfiguration(configuration) 62 | otpField.onOTPEnter = { [weak self] otpCode in 63 | guard otpCode != OTPFieldType.custom.password else { 64 | return 65 | } 66 | self?.otpField.clear() 67 | self?.otpField.setError() 68 | } 69 | otpField.onTextChanged = { [weak self] code in 70 | self?.otpField.removeError() 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOtpFieldViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOtpFieldViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldViewInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol CustomOtpFieldViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/CustomOtpField/View/CustomOtpFieldViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomOtpFieldViewOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol CustomOtpFieldViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/Configurator/DefaultOtpFieldModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldModuleConfigurator.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class DefaultOtpFieldModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, DefaultOtpFieldModuleOutput) { 14 | let view = DefaultOtpFieldViewController() 15 | let presenter = DefaultOtpFieldPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/Presenter/DefaultOtpFieldModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldModuleInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol DefaultOtpFieldModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/Presenter/DefaultOtpFieldModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldModuleOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol DefaultOtpFieldModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/Presenter/DefaultOtpFieldPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldPresenter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | final class DefaultOtpFieldPresenter: DefaultOtpFieldModuleOutput { 10 | 11 | // MARK: - DefaultOtpFieldModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: DefaultOtpFieldViewInput? 16 | 17 | } 18 | 19 | // MARK: - DefaultOtpFieldModuleInput 20 | 21 | extension DefaultOtpFieldPresenter: DefaultOtpFieldModuleInput { 22 | } 23 | 24 | // MARK: - DefaultOtpFieldViewOutput 25 | 26 | extension DefaultOtpFieldPresenter: DefaultOtpFieldViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/View/DefaultOtpFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldViewController.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class DefaultOtpFieldViewController: UIViewController { 13 | 14 | // MARK: - IBOutels 15 | 16 | @IBOutlet private weak var descriptionLabel: UILabel! 17 | @IBOutlet private weak var otpField: OTPTextField! 18 | @IBOutlet private weak var errorLabel: UILabel! 19 | 20 | // MARK: - Properties 21 | 22 | var output: DefaultOtpFieldViewOutput? 23 | 24 | // MARK: - UIViewController 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | output?.viewLoaded() 29 | } 30 | 31 | } 32 | 33 | // MARK: - DefaultOtpFieldViewInput 34 | 35 | extension DefaultOtpFieldViewController: DefaultOtpFieldViewInput { 36 | 37 | func setupInitialState() { 38 | configureNavigationBar() 39 | configureDescription() 40 | configureOtpField() 41 | configureErrorLabel() 42 | } 43 | 44 | } 45 | 46 | // MARK: - Configuration 47 | 48 | private extension DefaultOtpFieldViewController { 49 | 50 | func configureNavigationBar() { 51 | title = OTPFieldType.default.title 52 | } 53 | 54 | func configureDescription() { 55 | descriptionLabel.font = UIFont.systemFont(ofSize: 15, weight: .regular) 56 | descriptionLabel.textColor = Colors.Figma.defaultText 57 | descriptionLabel.text = OTPFieldType.default.description 58 | descriptionLabel.numberOfLines = 0 59 | } 60 | 61 | func configureOtpField() { 62 | otpField.onBeginEditing = { 63 | print("Did begin editing") 64 | } 65 | otpField.onOTPEnter = { [weak self] otpCode in 66 | guard otpCode == OTPFieldType.default.password else { 67 | self?.errorLabel.text = L10n.Errors.iccorectPassword 68 | self?.errorLabel.isHidden = false 69 | return 70 | } 71 | self?.errorLabel.isHidden = false 72 | self?.errorLabel.text = L10n.Errors.correctPassword 73 | } 74 | otpField.onTextChanged = { [weak self] text in 75 | self?.errorLabel.isHidden = true 76 | } 77 | } 78 | 79 | func configureErrorLabel() { 80 | errorLabel.textColor = Colors.Figma.defaultRed 81 | errorLabel.font = UIFont.systemFont(ofSize: 12.0, weight: .regular) 82 | errorLabel.text = nil 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/View/DefaultOtpFieldViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/View/DefaultOtpFieldViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldViewInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol DefaultOtpFieldViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/DefaultOtpField/View/DefaultOtpFieldViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultOtpFieldViewOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol DefaultOtpFieldViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/Configurator/MainModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleConfigurator.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class MainModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, MainModuleOutput) { 14 | let view = MainViewController() 15 | let presenter = MainPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | let navigationController = DefaultNavigationController(rootViewController: view) 20 | return (navigationController, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/Presenter/MainModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol MainModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/Presenter/MainModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol MainModuleOutput: class { 10 | var onOpenOTP: ((OTPFieldType) -> Void)? { get set } 11 | } 12 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/Presenter/MainPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainPresenter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | final class MainPresenter: MainModuleOutput { 10 | 11 | // MARK: - MainModuleOutput 12 | 13 | var onOpenOTP: ((OTPFieldType) -> Void)? 14 | 15 | // MARK: - Properties 16 | 17 | weak var view: MainViewInput? 18 | 19 | } 20 | 21 | // MARK: - MainModuleInput 22 | 23 | extension MainPresenter: MainModuleInput { 24 | } 25 | 26 | // MARK: - MainViewOutput 27 | 28 | extension MainPresenter: MainViewOutput { 29 | 30 | func viewLoaded() { 31 | view?.setupInitialState() 32 | } 33 | 34 | func openOTP(with type: OTPFieldType) { 35 | onOpenOTP?(type) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/View/Cells/MainFeedCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainFeedCell.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 20.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactiveDataDisplayManager 11 | 12 | final class MainFeedCell: UITableViewCell, Configurable { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var containerView: UIView! 17 | @IBOutlet private weak var nameLabel: UILabel! 18 | @IBOutlet private weak var arrowImageView: UIImageView! 19 | 20 | // MARK: - UITableViewCell 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | setupInitialState() 25 | } 26 | 27 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 28 | super.setHighlighted(highlighted, animated: animated) 29 | UIView.animate(withDuration: 0.3) { 30 | self.containerView.backgroundColor = highlighted 31 | ? Colors.Figma.negativeBackground.withAlphaComponent(0.6) 32 | : Colors.Figma.negativeBackground 33 | } 34 | } 35 | 36 | // MARK: - Configurable 37 | 38 | func configure(with model: String) { 39 | nameLabel.text = model 40 | } 41 | 42 | } 43 | 44 | // MARK: - Configuration 45 | 46 | private extension MainFeedCell { 47 | 48 | func setupInitialState() { 49 | configureContainer() 50 | configureNameLabel() 51 | configureArrowImageView() 52 | } 53 | 54 | func configureContainer() { 55 | selectionStyle = .none 56 | contentView.backgroundColor = Colors.Figma.defaultBackground 57 | containerView.backgroundColor = Colors.Figma.negativeBackground 58 | containerView.layer.cornerRadius = 5.0 59 | containerView.layer.masksToBounds = true 60 | } 61 | 62 | func configureNameLabel() { 63 | nameLabel.textColor = Colors.Figma.defaultText 64 | nameLabel.font = UIFont.systemFont(ofSize: 16, weight: .semibold) 65 | nameLabel.text = nil 66 | } 67 | 68 | func configureArrowImageView() { 69 | arrowImageView.contentMode = .scaleAspectFill 70 | arrowImageView.image = UIImage(asset: Asset.iconArrowRight) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/View/Cells/MainFeedCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/View/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ReactiveDataDisplayManager 11 | 12 | final class MainViewController: UIViewController { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var tableView: UITableView! 17 | 18 | // MARK: - Properties 19 | 20 | var output: MainViewOutput? 21 | 22 | // MARK: - Private Properties 23 | 24 | private lazy var adapter = BaseTableDataDisplayManager(collection: tableView) 25 | 26 | // MARK: - UIViewController 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | output?.viewLoaded() 31 | } 32 | 33 | } 34 | 35 | // MARK: - MainViewInput 36 | 37 | extension MainViewController: MainViewInput { 38 | 39 | func setupInitialState() { 40 | configureNavigationBar() 41 | configureTableView() 42 | fillTable() 43 | } 44 | 45 | } 46 | 47 | // MARK: - Configuration 48 | 49 | private extension MainViewController { 50 | 51 | func configureNavigationBar() { 52 | title = L10n.Main.title 53 | navigationController?.navigationBar.applyWhiteStyle() 54 | } 55 | 56 | func configureTableView() { 57 | tableView.alwaysBounceVertical = true 58 | tableView.separatorStyle = .none 59 | tableView.keyboardDismissMode = .onDrag 60 | tableView.backgroundColor = .clear 61 | tableView.showsVerticalScrollIndicator = false 62 | tableView.showsHorizontalScrollIndicator = false 63 | tableView.contentInset = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0) 64 | } 65 | 66 | func fillTable() { 67 | adapter.clearCellGenerators() 68 | OTPFieldType.allCases.forEach { type in 69 | let gen = BaseCellGenerator(with: type.title) 70 | gen.didSelectEvent += { [weak self] in 71 | self?.output?.openOTP(with: type) 72 | } 73 | adapter.addCellGenerator(gen) 74 | } 75 | adapter.forceRefill() 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/View/MainViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/View/MainViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol MainViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/Main/View/MainViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol MainViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | /// Notify presenter that view is ready to present otp screen 13 | func openOTP(with type: OTPFieldType) 14 | } 15 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/MainCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainCoordinator.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | final class MainCoordinator: BaseCoordinator, MainCoordinatorOutput { 10 | 11 | // MARK: - MainCoordinatorOutput 12 | 13 | // MARK: - Private Properties 14 | 15 | private let router = MainRouter() 16 | 17 | // MARK: - Coordinator 18 | 19 | override func start() { 20 | showModule() 21 | } 22 | 23 | } 24 | 25 | // MARK: - Private Methods 26 | 27 | private extension MainCoordinator { 28 | 29 | func showModule() { 30 | let (view, output) = MainModuleConfigurator().configure() 31 | output.onOpenOTP = { [weak self] type in 32 | self?.handleOpeningOtp(with: type) 33 | } 34 | router.setRootModule(view) 35 | } 36 | 37 | func showDefaultOtpScreen() { 38 | let (view, _) = DefaultOtpFieldModuleConfigurator().configure() 39 | router.push(view) 40 | } 41 | 42 | func showRoundOtpScreen() { 43 | let (view, _) = RoundOtpFieldModuleConfigurator().configure() 44 | router.push(view) 45 | } 46 | 47 | func showCustomOtpField() { 48 | let (view, _) = CustomOtpFieldModuleConfigurator().configure() 49 | router.push(view) 50 | } 51 | 52 | func showPlainOtpField() { 53 | let (view, _) = PlainOtpFieldModuleConfigurator().configure() 54 | router.push(view) 55 | } 56 | 57 | } 58 | 59 | // MARK: - Help Methods 60 | 61 | private extension MainCoordinator { 62 | 63 | func handleOpeningOtp(with type: OTPFieldType) { 64 | switch type { 65 | case .default: 66 | showDefaultOtpScreen() 67 | case .round: 68 | showRoundOtpScreen() 69 | case .custom: 70 | showCustomOtpField() 71 | case .plain: 72 | showPlainOtpField() 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/MainCoordinatorOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainCoordinatorOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol MainCoordinatorOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/Configurator/PlainOtpFieldModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldModuleConfigurator.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class PlainOtpFieldModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, PlainOtpFieldModuleOutput) { 14 | let view = PlainOtpFieldViewController() 15 | let presenter = PlainOtpFieldPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/Presenter/PlainOtpFieldModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldModuleInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol PlainOtpFieldModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/Presenter/PlainOtpFieldModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldModuleOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol PlainOtpFieldModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/Presenter/PlainOtpFieldPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldPresenter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | final class PlainOtpFieldPresenter: PlainOtpFieldModuleOutput { 10 | 11 | // MARK: - PlainOtpFieldModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: PlainOtpFieldViewInput? 16 | 17 | } 18 | 19 | // MARK: - PlainOtpFieldModuleInput 20 | 21 | extension PlainOtpFieldPresenter: PlainOtpFieldModuleInput { 22 | } 23 | 24 | // MARK: - PlainOtpFieldViewOutput 25 | 26 | extension PlainOtpFieldPresenter: PlainOtpFieldViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOTPField/PlainOTPFieldAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOTPFieldAdapter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class PlainOTPFieldAdapter: NSObject { 13 | 14 | // MARK: - Constants 15 | 16 | private enum Constants { 17 | static let numberOfPins = 6 18 | static let pinSize = CGSize(width: 32, height: 32) 19 | static let space: CGFloat = 0 20 | } 21 | 22 | } 23 | 24 | // MARK: - OTPTextFieldData 25 | 26 | extension PlainOTPFieldAdapter: OTPTextFieldData { 27 | 28 | public func numberOfPins() -> Int { 29 | return Constants.numberOfPins 30 | } 31 | 32 | public func otpTextField(viewAt index: Int) -> PinContainer { 33 | guard let pinView = PlainPinView.loadFromNib() as? PinContainer else { 34 | fatalError("Can't find class for init pinField") 35 | } 36 | return pinView 37 | } 38 | 39 | public func otpTextField(sizeForViewAt index: Int) -> CGSize { 40 | return Constants.pinSize 41 | } 42 | 43 | public func otpTextField(spaceForViewAt index: Int) -> CGFloat { 44 | return Constants.space 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOTPField/PlainPinView/PlainPinView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainPinView.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class PlainPinView: UIView { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var codeLabel: UILabel! 17 | @IBOutlet private weak var placeholderView: UIView! 18 | @IBOutlet private weak var indicatorView: UIView! 19 | 20 | // MARK: - UIView 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | setupInitialState() 25 | } 26 | 27 | } 28 | 29 | // MARK: - PinContainer 30 | 31 | extension PlainPinView: PinContainer { 32 | 33 | func set(value: String?) { 34 | codeLabel.text = value 35 | placeholderView.isHidden = true 36 | } 37 | 38 | func clear() { 39 | codeLabel.text = nil 40 | placeholderView.isHidden = false 41 | } 42 | 43 | func setupState(isActive: Bool, isError: Bool) { 44 | if isActive && indicatorView.isHidden { 45 | startIndicatorAnimation() 46 | } else if !isActive { 47 | stopIndicatorAnimation() 48 | } 49 | let color = isError ? Colors.Figma.defaultRed : Colors.Figma.negativeBackground 50 | placeholderView.backgroundColor = color 51 | } 52 | 53 | } 54 | 55 | // MARK: – Configuration 56 | 57 | private extension PlainPinView { 58 | 59 | func setupInitialState() { 60 | configureCodeLabel() 61 | configureIndicatorView() 62 | configurePlaceholderView() 63 | } 64 | 65 | func configureCodeLabel() { 66 | codeLabel.text = nil 67 | codeLabel.font = UIFont.systemFont(ofSize: 24, weight: .semibold) 68 | codeLabel.textColor = Colors.Figma.defaultText 69 | } 70 | 71 | func configureIndicatorView() { 72 | indicatorView.backgroundColor = Colors.Figma.defaultBlue 73 | indicatorView.layer.cornerRadius = 1 74 | indicatorView.isHidden = true 75 | } 76 | 77 | func configurePlaceholderView() { 78 | placeholderView.backgroundColor = Colors.Figma.negativeBackground 79 | placeholderView.layer.cornerRadius = placeholderView.bounds.height / 2 80 | } 81 | 82 | } 83 | 84 | // MARK: - Animation 85 | 86 | private extension PlainPinView { 87 | 88 | func startIndicatorAnimation() { 89 | let appearAnimation = CABasicAnimation(keyPath: "opacity") 90 | appearAnimation.fromValue = 0.0 91 | appearAnimation.toValue = 1.0 92 | appearAnimation.duration = 0.5 93 | appearAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 94 | 95 | let disappearAnimation = CABasicAnimation(keyPath: "opacity") 96 | disappearAnimation.fromValue = 1.0 97 | disappearAnimation.toValue = 0.0 98 | disappearAnimation.duration = 0.5 99 | disappearAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 100 | 101 | let animationGroup = CAAnimationGroup() 102 | animationGroup.animations = [appearAnimation, disappearAnimation] 103 | animationGroup.duration = 1 104 | animationGroup.repeatCount = .infinity 105 | 106 | indicatorView.isHidden = false 107 | indicatorView.layer.add(animationGroup, forKey: "fade") 108 | } 109 | 110 | func stopIndicatorAnimation() { 111 | indicatorView.isHidden = true 112 | indicatorView.layer.removeAllAnimations() 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOTPField/PlainPinView/PlainPinView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOtpFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldViewController.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class PlainOtpFieldViewController: UIViewController { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var descriptionLabel: UILabel! 17 | @IBOutlet private weak var otpField: OTPTextField! 18 | 19 | // MARK: - Properties 20 | 21 | var output: PlainOtpFieldViewOutput? 22 | 23 | // MARK: - UIViewController 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | output?.viewLoaded() 28 | } 29 | 30 | } 31 | 32 | // MARK: - PlainOtpFieldViewInput 33 | 34 | extension PlainOtpFieldViewController: PlainOtpFieldViewInput { 35 | 36 | func setupInitialState() { 37 | configureNavigationBar() 38 | configureDescription() 39 | configureOtpField() 40 | } 41 | 42 | } 43 | 44 | // MARK: - Configuration 45 | 46 | private extension PlainOtpFieldViewController { 47 | 48 | func configureNavigationBar() { 49 | title = OTPFieldType.plain.title 50 | } 51 | 52 | func configureDescription() { 53 | descriptionLabel.font = UIFont.systemFont(ofSize: 15, weight: .regular) 54 | descriptionLabel.textColor = Colors.Figma.defaultText 55 | descriptionLabel.text = OTPFieldType.plain.description 56 | descriptionLabel.numberOfLines = 0 57 | } 58 | 59 | func configureOtpField() { 60 | let configuration = OTPFieldConfiguration(adapter: PlainOTPFieldAdapter()) 61 | otpField.setConfiguration(configuration) 62 | otpField.onOTPEnter = { [weak self] otpCode in 63 | guard otpCode != OTPFieldType.plain.password else { 64 | return 65 | } 66 | self?.otpField.clear() 67 | self?.otpField.setError() 68 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in 69 | self?.otpField.removeError() 70 | } 71 | } 72 | otpField.onTextChanged = { [weak self] code in 73 | self?.otpField.removeError() 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOtpFieldViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOtpFieldViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldViewInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol PlainOtpFieldViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/PlainOtpField/View/PlainOtpFieldViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlainOtpFieldViewOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Александр Чаусов on 25/07/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol PlainOtpFieldViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/Configurator/RoundOtpFieldModuleConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldModuleConfigurator.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class RoundOtpFieldModuleConfigurator { 12 | 13 | func configure() -> (UIViewController, RoundOtpFieldModuleOutput) { 14 | let view = RoundOtpFieldViewController() 15 | let presenter = RoundOtpFieldPresenter() 16 | 17 | presenter.view = view 18 | view.output = presenter 19 | 20 | return (view, presenter) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/Presenter/RoundOtpFieldModuleInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldModuleInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol RoundOtpFieldModuleInput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/Presenter/RoundOtpFieldModuleOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldModuleOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol RoundOtpFieldModuleOutput: class { 10 | } 11 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/Presenter/RoundOtpFieldPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldPresenter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | final class RoundOtpFieldPresenter: RoundOtpFieldModuleOutput { 10 | 11 | // MARK: - RoundOtpFieldModuleOutput 12 | 13 | // MARK: - Properties 14 | 15 | weak var view: RoundOtpFieldViewInput? 16 | 17 | } 18 | 19 | // MARK: - RoundOtpFieldModuleInput 20 | 21 | extension RoundOtpFieldPresenter: RoundOtpFieldModuleInput { 22 | } 23 | 24 | // MARK: - RoundOtpFieldViewOutput 25 | 26 | extension RoundOtpFieldPresenter: RoundOtpFieldViewOutput { 27 | 28 | func viewLoaded() { 29 | view?.setupInitialState() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/View/RoundOtpFieldViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldViewController.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFOTPTextField 11 | 12 | final class RoundOtpFieldViewController: UIViewController { 13 | 14 | // MARK: - IBOutlets 15 | 16 | @IBOutlet private weak var descriptionLabel: UILabel! 17 | @IBOutlet private weak var otpField: OTPTextField! 18 | 19 | // MARK: - Properties 20 | 21 | var output: RoundOtpFieldViewOutput? 22 | 23 | // MARK: - UIViewController 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | output?.viewLoaded() 28 | } 29 | 30 | } 31 | 32 | // MARK: - RoundOtpFieldViewInput 33 | 34 | extension RoundOtpFieldViewController: RoundOtpFieldViewInput { 35 | 36 | func setupInitialState() { 37 | configureNavigationBar() 38 | configureDescription() 39 | configureOtpField() 40 | } 41 | 42 | } 43 | 44 | // MARK: - Configuration 45 | 46 | private extension RoundOtpFieldViewController { 47 | 48 | func configureNavigationBar() { 49 | title = OTPFieldType.round.title 50 | } 51 | 52 | func configureDescription() { 53 | descriptionLabel.font = UIFont.systemFont(ofSize: 15, weight: .regular) 54 | descriptionLabel.textColor = Colors.Figma.defaultText 55 | descriptionLabel.text = OTPFieldType.round.description 56 | descriptionLabel.numberOfLines = 0 57 | } 58 | 59 | func configureOtpField() { 60 | let configuration = OTPFieldConfiguration(adapter: RoundTextFieldAdapter(), 61 | keyboardType: .namePhonePad, 62 | keyboardAppearance: .light, 63 | autocorrectionType: .no, 64 | allowedCharactersSet: .alphanumerics) 65 | otpField.setConfiguration(configuration) 66 | otpField.onOTPEnter = { [weak self] otpCode in 67 | guard otpCode != OTPFieldType.round.password else { 68 | return 69 | } 70 | self?.otpField.setError() 71 | } 72 | otpField.onTextChanged = { [weak self] code in 73 | self?.otpField.removeError() 74 | } 75 | } 76 | 77 | } 78 | 79 | // MARK: - Actions 80 | 81 | private extension RoundOtpFieldViewController { 82 | 83 | @IBAction private func hideKeyboardDidPressed(_ sender: Any) { 84 | view.endEditing(true) 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/View/RoundOtpFieldViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/View/RoundOtpFieldViewInput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldViewInput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol RoundOtpFieldViewInput: class { 10 | /// Method for setup initial state of view 11 | func setupInitialState() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Flows/Main/RoundOtpField/View/RoundOtpFieldViewOutput.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundOtpFieldViewOutput.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | protocol RoundOtpFieldViewOutput { 10 | /// Notify presenter that view is ready 11 | func viewLoaded() 12 | } 13 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Base Classes/BaseCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | /// Provides base realisation of coordinator's methods 8 | /// Subclass from this base class for making new flow coordinator 9 | class BaseCoordinator: Coordinator { 10 | 11 | // MARK: - Properties 12 | 13 | var childCoordinators: [Coordinator] = [] 14 | 15 | // MARK: - Internal Methods 16 | 17 | func start() { 18 | start(with: nil) 19 | } 20 | 21 | func start(with deepLinkOption: DeepLinkOption?) { } 22 | 23 | // add only unique object 24 | func addDependency(_ coordinator: Coordinator) { 25 | guard !haveDependency(coordinator) else { 26 | return 27 | } 28 | childCoordinators.append(coordinator) 29 | } 30 | 31 | func removeDependency(_ coordinator: Coordinator?) { 32 | guard 33 | !childCoordinators.isEmpty, 34 | let coordinator = coordinator 35 | else { return } 36 | 37 | for (index, element) in childCoordinators.enumerated() { 38 | if element === coordinator { 39 | childCoordinators.remove(at: index) 40 | break 41 | } 42 | } 43 | } 44 | 45 | func removeAllChilds() { 46 | guard 47 | !childCoordinators.isEmpty 48 | else { return } 49 | 50 | for coordinator in childCoordinators { 51 | if let coordinator = coordinator as? BaseCoordinator { 52 | coordinator.removeAllChilds() 53 | } 54 | } 55 | 56 | childCoordinators.removeAll() 57 | } 58 | 59 | // MARK: - Private Methods 60 | 61 | private func haveDependency(_ coordinator: Coordinator) -> Bool { 62 | for element in childCoordinators { 63 | if element === coordinator { 64 | return true 65 | } 66 | } 67 | return false 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Base Classes/DefaultNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultNavigationController.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 20.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Base navigation controller into the application 12 | public class DefaultNavigationController: UINavigationController { 13 | 14 | // MARK: - UIViewController 15 | 16 | override public func viewDidLoad() { 17 | super.viewDidLoad() 18 | self.delegate = self 19 | } 20 | 21 | } 22 | 23 | // MARK: - UINavigationControllerDelegate 24 | 25 | extension DefaultNavigationController: UINavigationControllerDelegate { 26 | 27 | public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { 28 | // removing "Back" word from back navigation bar button 29 | navigationController.topViewController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Enums/OTPFieldType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPFieldType.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 20.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum OTPFieldType: CaseIterable { 12 | case `default` 13 | case round 14 | case custom 15 | case plain 16 | 17 | var title: String { 18 | switch self { 19 | case .default: 20 | return L10n.OTPFieldType.Default.title 21 | case .round: 22 | return L10n.OTPFieldType.Round.title 23 | case .custom: 24 | return L10n.OTPFieldType.Custom.title 25 | case .plain: 26 | return L10n.OTPFieldType.Plain.title 27 | } 28 | } 29 | 30 | var description: String { 31 | switch self { 32 | case .default: 33 | return L10n.OTPFieldType.Default.description(password) 34 | case .round: 35 | return L10n.OTPFieldType.Round.description(password) 36 | case .custom: 37 | return L10n.OTPFieldType.Custom.description(password) 38 | case .plain: 39 | return L10n.OTPFieldType.Plain.description(password) 40 | } 41 | } 42 | 43 | var password: String { 44 | switch self { 45 | case .default: 46 | return "1234" 47 | case .round: 48 | return "123456" 49 | case .custom: 50 | return "1234" 51 | case .plain: 52 | return "123456" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Extensions/UIKit/NavigationBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBar.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 20.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UINavigationBar { 12 | 13 | func applyWhiteStyle() { 14 | barTintColor = Colors.Figma.defaultBackground 15 | tintColor = Colors.Figma.defaultRed 16 | titleTextAttributes = [ 17 | .foregroundColor: Colors.Figma.defaultText, 18 | .font: UIFont.systemFont(ofSize: 18, weight: .semibold) 19 | ] 20 | barStyle = .default 21 | backIndicatorImage = UIImage(asset: Asset.iconBackLeft) 22 | backIndicatorTransitionMaskImage = UIImage(asset: Asset.iconBackLeft) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Extensions/UIKit/UIApplication.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | extension UIApplication { 8 | 9 | static func topViewController(_ controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { 10 | if let navigationController = controller as? UINavigationController { 11 | return topViewController(navigationController.visibleViewController) 12 | } 13 | if let tabController = controller as? UITabBarController { 14 | if let selected = tabController.selectedViewController { 15 | return topViewController(selected) 16 | } 17 | } 18 | if let presented = controller?.presentedViewController { 19 | return topViewController(presented) 20 | } 21 | return controller 22 | } 23 | 24 | static func appVersion() -> String? { 25 | let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String 26 | return appVersion 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Protocols/.gitkeep: -------------------------------------------------------------------------------- 1 | This file is used to create folders structure. -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Protocols/Coordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | 7 | /// Base protocol for coordinator 8 | protocol Coordinator: class { 9 | /// Notifies coordinator that it can start itself 10 | func start() 11 | /// Notifies coordinator that it should start itself with deeplink option 12 | /// 13 | /// - parameter deepLinkOption: deeplink option such as Dynamic Link, push-notification, etc. 14 | func start(with deepLinkOption: DeepLinkOption?) 15 | /// Notifies coordinator that it should remove all child coordinators 16 | func removeAllChilds() 17 | } 18 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Protocols/Presentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | /// Describes object that can be presented in view hierarchy 8 | protocol Presentable { 9 | func toPresent() -> UIViewController? 10 | } 11 | 12 | extension UIViewController: Presentable { 13 | 14 | func toPresent() -> UIViewController? { 15 | return self 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Protocols/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | /// Describes object that handles all navigation operations 6 | protocol Router { 7 | func present(_ module: Presentable?) 8 | func present(_ module: Presentable?, animated: Bool, completion: (() -> Void)?) 9 | 10 | func push(_ module: Presentable?) 11 | func push(_ module: Presentable?, animated: Bool) 12 | 13 | func popModule() 14 | func popModule(animated: Bool) 15 | func popPreviousView() 16 | 17 | func dismissModule() 18 | func dismissModule(animated: Bool, completion: (() -> Void)?) 19 | 20 | func setNavigationControllerRootModule(_ module: Presentable?, animated: Bool, hideBar: Bool) 21 | func setRootModule(_ module: Presentable?) 22 | 23 | func setTab(_ index: Int) 24 | } 25 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Reusable Layer/.gitkeep: -------------------------------------------------------------------------------- 1 | This file is used to create folders structure. -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Routers/.gitkeep: -------------------------------------------------------------------------------- 1 | This file is used to create folders structure. -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Routers/MainRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | import UIKit 6 | 7 | /// Provides methods and properties for all navigation operations. 8 | /// Instantiate, and use the object of this class in coordinators. 9 | class MainRouter: Router { 10 | 11 | private var window: UIWindow? { 12 | return (UIApplication.shared.delegate as? AppDelegate)?.window 13 | } 14 | 15 | private var navigationController: UINavigationController? { 16 | if let tabBar = UIApplication.shared.keyWindow?.rootViewController as? UITabBarController { 17 | return tabBar.selectedViewController as? UINavigationController 18 | } 19 | return UIApplication.shared.keyWindow?.rootViewController as? UINavigationController 20 | } 21 | 22 | private var tabBarController: UITabBarController? { 23 | return UIApplication.shared.keyWindow?.rootViewController as? UITabBarController 24 | } 25 | 26 | private var topViewController: UIViewController? { 27 | return UIApplication.topViewController() 28 | } 29 | 30 | func present(_ module: Presentable?) { 31 | self.present(module, animated: true, completion: nil) 32 | } 33 | 34 | func present(_ module: Presentable?, animated: Bool, completion: (() -> Void)?) { 35 | if let controller = module?.toPresent() { 36 | self.topViewController?.present(controller, animated: animated, completion: completion) 37 | } 38 | } 39 | 40 | func push(_ module: Presentable?) { 41 | self.push(module, animated: true) 42 | } 43 | 44 | func push(_ module: Presentable?, animated: Bool) { 45 | if let controller = module?.toPresent() { 46 | self.topViewController?.navigationController?.pushViewController(controller, animated: animated) 47 | } 48 | } 49 | 50 | func popModule() { 51 | self.popModule(animated: true) 52 | } 53 | 54 | func popModule(animated: Bool) { 55 | self.navigationController?.popViewController(animated: animated) 56 | } 57 | 58 | func popPreviousView() { 59 | let navigationController = self.topViewController?.navigationController 60 | guard 61 | var controllers = navigationController?.viewControllers, 62 | controllers.count >= 3 else { 63 | return 64 | } 65 | controllers.remove(at: controllers.count - 2) 66 | navigationController?.viewControllers = controllers 67 | } 68 | 69 | func dismissModule() { 70 | self.dismissModule(animated: true, completion: nil) 71 | } 72 | 73 | func dismissModule(animated: Bool, completion: (() -> Void)?) { 74 | topViewController?.dismiss(animated: animated, completion: completion) 75 | } 76 | 77 | func setNavigationControllerRootModule(_ module: Presentable?, animated: Bool = false, hideBar: Bool = false) { 78 | if let controller = module?.toPresent() { 79 | navigationController?.isNavigationBarHidden = hideBar 80 | navigationController?.setViewControllers([controller], animated: false) 81 | } 82 | } 83 | 84 | func setRootModule(_ module: Presentable?) { 85 | window?.rootViewController = module?.toPresent() 86 | } 87 | 88 | func setTab(_ index: Int) { 89 | tabBarController?.selectedIndex = index 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Library/Utils/.gitkeep: -------------------------------------------------------------------------------- 1 | This file is used to create folders structure. -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Colors/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2020 Fixique. All rights reserved. 3 | // 4 | 5 | enum Colors { 6 | 7 | enum Figma { 8 | static let defaultBackground = Asset.Colors.background.color 9 | static let negativeBackground = Asset.Colors.contaienrBackground.color 10 | static let defaultText = Asset.Colors.textColor.color 11 | static let defaultRed = Asset.Colors.red.color 12 | static let defaultBlue = Asset.Colors.defaultBlue.color 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Constants/.gitkeep: -------------------------------------------------------------------------------- 1 | This file is used to create folders structure. -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Fonts/.gitkeep -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Fonts/Fonts.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | // No fonts found 5 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | #if os(OSX) 5 | import AppKit.NSImage 6 | internal typealias AssetColorTypeAlias = NSColor 7 | internal typealias AssetImageTypeAlias = NSImage 8 | #elseif os(iOS) || os(tvOS) || os(watchOS) 9 | import UIKit.UIImage 10 | internal typealias AssetColorTypeAlias = UIColor 11 | internal typealias AssetImageTypeAlias = UIImage 12 | #endif 13 | 14 | // swiftlint:disable superfluous_disable_command 15 | // swiftlint:disable file_length 16 | 17 | // MARK: - Asset Catalogs 18 | 19 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 20 | internal enum Asset { 21 | internal enum Colors { 22 | 23 | internal static let textColor = ColorAsset(name: "Colors/TextColor") 24 | internal static let background = ColorAsset(name: "Colors/background") 25 | internal static let contaienrBackground = ColorAsset(name: "Colors/contaienrBackground") 26 | internal static let defaultBlue = ColorAsset(name: "Colors/defaultBlue") 27 | internal static let red = ColorAsset(name: "Colors/red") 28 | } 29 | internal static let iconArrowRight = ImageAsset(name: "icon-arrow-right") 30 | internal static let iconBackLeft = ImageAsset(name: "icon-back-left") 31 | } 32 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 33 | 34 | // MARK: - Implementation Details 35 | 36 | internal struct ColorAsset { 37 | internal fileprivate(set) var name: String 38 | 39 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, OSX 10.13, *) 40 | internal var color: AssetColorTypeAlias { 41 | return AssetColorTypeAlias(asset: self) 42 | } 43 | } 44 | 45 | internal extension AssetColorTypeAlias { 46 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, OSX 10.13, *) 47 | convenience init!(asset: ColorAsset) { 48 | let bundle = Bundle(for: BundleToken.self) 49 | #if os(iOS) || os(tvOS) 50 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 51 | #elseif os(OSX) 52 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 53 | #elseif os(watchOS) 54 | self.init(named: asset.name) 55 | #endif 56 | } 57 | } 58 | 59 | internal struct DataAsset { 60 | internal fileprivate(set) var name: String 61 | 62 | #if os(iOS) || os(tvOS) || os(OSX) 63 | @available(iOS 9.0, tvOS 9.0, OSX 10.11, *) 64 | internal var data: NSDataAsset { 65 | return NSDataAsset(asset: self) 66 | } 67 | #endif 68 | } 69 | 70 | #if os(iOS) || os(tvOS) || os(OSX) 71 | @available(iOS 9.0, tvOS 9.0, OSX 10.11, *) 72 | internal extension NSDataAsset { 73 | convenience init!(asset: DataAsset) { 74 | let bundle = Bundle(for: BundleToken.self) 75 | #if os(iOS) || os(tvOS) 76 | self.init(name: asset.name, bundle: bundle) 77 | #elseif os(OSX) 78 | self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) 79 | #endif 80 | } 81 | } 82 | #endif 83 | 84 | internal struct ImageAsset { 85 | internal fileprivate(set) var name: String 86 | 87 | internal var image: AssetImageTypeAlias { 88 | let bundle = Bundle(for: BundleToken.self) 89 | #if os(iOS) || os(tvOS) 90 | let image = AssetImageTypeAlias(named: name, in: bundle, compatibleWith: nil) 91 | #elseif os(OSX) 92 | let image = bundle.image(forResource: NSImage.Name(name)) 93 | #elseif os(watchOS) 94 | let image = AssetImageTypeAlias(named: name) 95 | #endif 96 | guard let result = image else { fatalError("Unable to load image named \(name).") } 97 | return result 98 | } 99 | } 100 | 101 | internal extension AssetImageTypeAlias { 102 | @available(iOS 1.0, tvOS 1.0, watchOS 1.0, *) 103 | @available(OSX, deprecated, 104 | message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") 105 | convenience init!(asset: ImageAsset) { 106 | #if os(iOS) || os(tvOS) 107 | let bundle = Bundle(for: BundleToken.self) 108 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 109 | #elseif os(OSX) 110 | self.init(named: NSImage.Name(asset.name)) 111 | #elseif os(watchOS) 112 | self.init(named: asset.name) 113 | #endif 114 | } 115 | } 116 | 117 | private final class BundleToken {} 118 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Colors/TextColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x22", 13 | "alpha" : "1.000", 14 | "blue" : "0x22", 15 | "green" : "0x22" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Colors/background.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xFF", 13 | "alpha" : "1.000", 14 | "blue" : "0xFF", 15 | "green" : "0xFF" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Colors/contaienrBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xF2", 13 | "alpha" : "1.000", 14 | "blue" : "0xF2", 15 | "green" : "0xF2" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Colors/defaultBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0x32", 13 | "alpha" : "1.000", 14 | "blue" : "0x9C", 15 | "green" : "0x00" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Colors/red.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0xD4", 13 | "alpha" : "1.000", 14 | "blue" : "0x00", 15 | "green" : "0x00" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-arrow-right.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-arrow-right@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-arrow-right@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/icon-arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/icon-arrow-right.png -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/icon-arrow-right@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/icon-arrow-right@2x.png -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/icon-arrow-right@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-arrow-right.imageset/icon-arrow-right@3x.png -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-back-left.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-back-left@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icon-back-left@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/icon-back-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/icon-back-left.png -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/icon-back-left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/icon-back-left@2x.png -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/icon-back-left@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Example/OTPTextFieldExample/Resources/Images/Assets.xcassets/icon-back-left.imageset/icon-back-left@3x.png -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Strings/Localizable.strings: -------------------------------------------------------------------------------- 1 | 2 | // MARK: - Main 3 | 4 | "Main.Title" = "Examples"; 5 | 6 | // MARK: - OTPFieldType 7 | 8 | "OTPFieldType.Default.title" = "Default input field"; 9 | "OTPFieldType.Round.title" = "Round input field"; 10 | "OTPFieldType.Custom.title" = "Custom input field"; 11 | "OTPFieldType.Plain.title" = "Plain input field"; 12 | 13 | "OTPFieldType.Default.description" = "This is an example of a default input field. An error state has been added to it. Correct password is %@"; 14 | "OTPFieldType.Round.description" = "This is an example of a default rounded input field. An error state has been added to it. Correct password is %@"; 15 | "OTPFieldType.Custom.description" = "This is an example of a fully custom input field otp. An error state has been added to it. I create a custom pin view and adapter. Correct password is %@"; 16 | "OTPFieldType.Plain.description" = "This is an example of a fully custom input field otp. Very similar to customOtpField. Has custom behavior, due to which the error is reset on its own after two seconds. Correct password is %@"; 17 | 18 | // MARK: - Errors 19 | 20 | "Errors.iccorectPassword" = "Incorrect password"; 21 | "Errors.correctPassword" = "Correct password"; 22 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExample/Resources/Strings/Strings.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | import Foundation 5 | 6 | // swiftlint:disable superfluous_disable_command 7 | // swiftlint:disable file_length 8 | 9 | // MARK: - Strings 10 | 11 | // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length 12 | // swiftlint:disable nesting type_body_length type_name 13 | internal enum L10n { 14 | 15 | internal enum Errors { 16 | /// Correct password 17 | internal static let correctPassword = L10n.tr("Localizable", "Errors.correctPassword") 18 | /// Incorrect password 19 | internal static let iccorectPassword = L10n.tr("Localizable", "Errors.iccorectPassword") 20 | } 21 | 22 | internal enum Main { 23 | /// Examples 24 | internal static let title = L10n.tr("Localizable", "Main.Title") 25 | } 26 | 27 | internal enum OTPFieldType { 28 | internal enum Custom { 29 | /// This is an example of a fully custom input field otp. An error state has been added to it. I create a custom pin view and adapter. Correct password is %@ 30 | internal static func description(_ p1: String) -> String { 31 | return L10n.tr("Localizable", "OTPFieldType.Custom.description", p1) 32 | } 33 | /// Custom input field 34 | internal static let title = L10n.tr("Localizable", "OTPFieldType.Custom.title") 35 | } 36 | internal enum Default { 37 | /// This is an example of a default input field. An error state has been added to it. Correct password is %@ 38 | internal static func description(_ p1: String) -> String { 39 | return L10n.tr("Localizable", "OTPFieldType.Default.description", p1) 40 | } 41 | /// Default input field 42 | internal static let title = L10n.tr("Localizable", "OTPFieldType.Default.title") 43 | } 44 | internal enum Plain { 45 | /// This is an example of a fully custom input field otp. Very similar to customOtpField. Has custom behavior, due to which the error is reset on its own after two seconds. Correct password is %@ 46 | internal static func description(_ p1: String) -> String { 47 | return L10n.tr("Localizable", "OTPFieldType.Plain.description", p1) 48 | } 49 | /// Plain input field 50 | internal static let title = L10n.tr("Localizable", "OTPFieldType.Plain.title") 51 | } 52 | internal enum Round { 53 | /// This is an example of a default rounded input field. An error state has been added to it. Correct password is %@ 54 | internal static func description(_ p1: String) -> String { 55 | return L10n.tr("Localizable", "OTPFieldType.Round.description", p1) 56 | } 57 | /// Round input field 58 | internal static let title = L10n.tr("Localizable", "OTPFieldType.Round.title") 59 | } 60 | } 61 | } 62 | // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length 63 | // swiftlint:enable nesting type_body_length type_name 64 | 65 | // MARK: - Implementation Details 66 | 67 | extension L10n { 68 | private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { 69 | // swiftlint:disable:next nslocalizedstring_key 70 | let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "") 71 | return String(format: format, locale: Locale.current, arguments: args) 72 | } 73 | } 74 | 75 | private final class BundleToken {} 76 | -------------------------------------------------------------------------------- /Example/OTPTextFieldExampleTests/Tests/Flows/Main/Main/Configurator/MainModuleConfiguratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModuleConfiguratorTests.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Krupenko Validislav on 20/03/2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import OTPTextFieldExample 11 | 12 | final class MainModuleConfiguratorTests: XCTestCase { 13 | 14 | // MARK: - Main tests 15 | 16 | func testDeallocation() { 17 | assertDeallocation(of: { 18 | let (view, output) = MainModuleConfigurator().configure() 19 | return (view, [output]) 20 | }) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11.0' 2 | 3 | def utils 4 | pod 'SwiftGen', '6.1.0' 5 | pod 'SwiftLint', '0.30.1' 6 | end 7 | 8 | def common_pods 9 | utils 10 | pod 'SurfUtils/XibView', :git => 'https://github.com/surfstudio/iOS-Utils.git', :tag => '10.0.5' 11 | pod 'ReactiveDataDisplayManager', :git => 'https://github.com/surfstudio/ReactiveDataDisplayManager', :commit => '73122a834e7e90744dd828804afb32dbd48a83c6' 12 | pod 'SFOTPTextField', :path => '../' 13 | 14 | end 15 | 16 | target 'OTPTextFieldExample' do 17 | use_frameworks! 18 | common_pods 19 | end 20 | -------------------------------------------------------------------------------- /Example/README.md: -------------------------------------------------------------------------------- 1 | # OTPTextFieldExample 2 | Little description 3 | 4 | ## Requirements 5 | * iOS 10.0+ 6 | * Xcode 9.0 7 | 8 | ## Before running 9 | For install all dependencies you should open project folder in **Terminal** and call: `make init` 10 | 11 | ## Module generation 12 | Before generation you should open project folder in **Terminal** 13 | * Screen generation: `make screen modName=[SCREEN_NAME]` -------------------------------------------------------------------------------- /Example/Rambafile: -------------------------------------------------------------------------------- 1 | ### Headers settings 2 | company: Fixique 3 | 4 | ### Xcode project settings 5 | project_name: OTPTextFieldExample 6 | xcodeproj_path: OTPTextFieldExample.xcodeproj 7 | 8 | ### Code generation settings section 9 | # The main project target name 10 | project_target: OTPTextFieldExample 11 | 12 | # The file path for new modules 13 | project_file_path: OTPTextFieldExample/Flows 14 | 15 | # The Xcode group path to new modules 16 | project_group_path: OTPTextFieldExample/Flows 17 | 18 | ### Dependencies settings section 19 | podfile_path: Podfile 20 | 21 | ### Catalogs 22 | catalogs: 23 | - 'https://github.com/chausovSurfStudio/generamba-templates' 24 | 25 | ### Templates 26 | templates: 27 | - {name: surf_mvp_coordinatable_module} 28 | - {name: surf_mvp_coordinatable_alert} 29 | - {name: surf_mvp_coordinator} -------------------------------------------------------------------------------- /Example/ci/JenkinsfilePullRequestJob.groovy: -------------------------------------------------------------------------------- 1 | @Library('surf-lib@version-1.0.0-SNAPSHOT') // https://bitbucket.org/surfstudio/jenkins-pipeline-lib/ 2 | import ru.surfstudio.ci.pipeline.pr.PrPipelineiOS 3 | import ru.surfstudio.ci.stage.StageStrategy 4 | import ru.surfstudio.ci.CommonUtil 5 | 6 | //init 7 | def pipeline = new PrPipelineiOS(this) 8 | pipeline.init() 9 | 10 | //customization 11 | pipeline.getStage(pipeline.UNIT_TEST).body = { 12 | CommonUtil.shWithRuby(this, "make test") 13 | junit 'fastlane/test_output/report.junit' 14 | archiveArtifacts artifacts: 'fastlane/test_output/report.html' 15 | } 16 | pipeline.getStage(pipeline.UNIT_TEST).strategy = StageStrategy.FAIL_WHEN_STAGE_ERROR 17 | pipeline.getStage(pipeline.INSTRUMENTATION_TEST).strategy = StageStrategy.SKIP_STAGE 18 | 19 | //run 20 | pipeline.run() -------------------------------------------------------------------------------- /Example/ci/JenkinsfileTagJob.groovy: -------------------------------------------------------------------------------- 1 | @Library('surf-lib@version-1.0.0-SNAPSHOT') // https://bitbucket.org/surfstudio/jenkins-pipeline-lib/ 2 | import ru.surfstudio.ci.pipeline.tag.TagPipelineiOS 3 | import ru.surfstudio.ci.stage.StageStrategy 4 | 5 | //init 6 | def pipeline = new TagPipelineiOS(this) 7 | pipeline.init() 8 | 9 | //customization 10 | pipeline.getStage(pipeline.BUILD).strategy = StageStrategy.SKIP_STAGE 11 | pipeline.getStage(pipeline.UNIT_TEST).strategy = StageStrategy.SKIP_STAGE 12 | pipeline.getStage(pipeline.INSTRUMENTATION_TEST).strategy = StageStrategy.SKIP_STAGE 13 | pipeline.getStage(pipeline.STATIC_CODE_ANALYSIS).strategy = StageStrategy.SKIP_STAGE 14 | 15 | //run 16 | pipeline.run() -------------------------------------------------------------------------------- /Example/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # regex to validate in commit msg 4 | commit_regex='([A-Z]{1,}-[0-9]{1,}\s.+|Merge.*|Version Bump.*)' 5 | error_msg="Aborting commit. Your commit message is missing either a Jira Issue ('PROJ-99') or 'Merge'" 6 | 7 | if ! grep -iqE "$commit_regex" "$1"; then 8 | echo "$error_msg" >&2 9 | exit 1 10 | fi -------------------------------------------------------------------------------- /Example/fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier "[[APP_IDENTIFIER]]" # The bundle identifier of your app 2 | apple_id "PUT HERE EMAIL" # Your Apple email address 3 | 4 | team_id "PUT HERE TEAM ID" # Developer Portal Team ID 5 | 6 | # For more information about the Appfile, see: 7 | # https://docs.fastlane.tools/advanced/#appfile 8 | -------------------------------------------------------------------------------- /Example/fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # Customise this file, documentation can be found here: 2 | # https://docs.fastlane.tools/actions/ 3 | # All available actions: https://docs.fastlane.tools/actions 4 | # can also be listed using the `fastlane actions` command 5 | 6 | # Change the syntax highlighting to Ruby 7 | # All lines starting with a # are ignored when running `fastlane` 8 | 9 | # If you want to automatically update fastlane if a new version is available: 10 | # update_fastlane 11 | 12 | default_platform :ios 13 | 14 | platform :ios do 15 | 16 | workspace_file = 'OTPTextFieldExample.xcworkspace' 17 | project_file = 'OTPTextFieldExample.xcodeproj' 18 | prod_scheme = 'OTPTextFieldExample' 19 | bundle_name = "ru.fixique.OTPTextFieldExample" 20 | derived_data_path = "./buildData" 21 | 22 | before_all do |lane, options| 23 | # ensure_git_status_clean 24 | end 25 | 26 | desc "Build Main scheme of Xcode target in Debug configuration" 27 | desc "Parameters: 28 | - clean: Pass **true** if you need to clean project before build 29 | " 30 | lane :build do |options| 31 | xcodebuild( 32 | workspace: workspace_file, 33 | scheme: prod_scheme, 34 | configuration: "Debug", 35 | clean: options[:clean], 36 | build: true, 37 | destination: "generic/platform=iOS\" CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=\"", 38 | xcargs: "-sdk iphonesimulator ONLY_ACTIVE_ARCH=NO build-for-testing", 39 | derivedDataPath: derived_data_path 40 | ) 41 | end 42 | 43 | desc "Run tests" 44 | lane :tests do 45 | run_tests( 46 | workspace: workspace_file, 47 | devices: ["iPhone SE"], 48 | scheme: prod_scheme, 49 | code_coverage: false, 50 | test_without_building: true, 51 | skip_build: true, 52 | derived_data_path: derived_data_path, 53 | output_types: "html,junit" 54 | ) 55 | end 56 | 57 | desc "Update build number, set tag and push version bump commit to the develop branch if needed" 58 | desc "Parameters: 59 | - version_number: Number of version that will be set 60 | - type: Type of the build which will be set with the tag. For Example: for release candidate builds with tag `1.0.0-rc10` you need to pass `rc`. 61 | - type_v: Version of current build type (type parameter). 62 | - skip_push: Pass **true** if you need to skip push commits to the repository. The default is **false** 63 | " 64 | lane :prepare_for_integration do |options| 65 | version_number = options[:version_number] 66 | if version_number.nil? 67 | throw "Required version_number parameter" 68 | end 69 | 70 | build_number = options[:type_v] 71 | if not build_number.nil? 72 | increment_build_number_in_plist(build_number: build_number, target: prod_scheme) 73 | else 74 | increment_build_number_in_plist(target: prod_scheme) 75 | end 76 | 77 | increment_version_number_in_plist(version_number: options[:version_number], target: prod_scheme) 78 | 79 | commit_version_bump( 80 | xcodeproj: project_file, 81 | force: true 82 | ) 83 | 84 | tag_label = get_version_number_from_plist(target: prod_scheme) 85 | 86 | type = options[:type] 87 | typeIndex = options[:type_v] 88 | if not type.nil? 89 | tag_label = "v" + tag_label + "-" + type + "" + typeIndex 90 | end 91 | 92 | add_git_tag( 93 | tag: tag_label 94 | ) 95 | 96 | sh("git push && git push origin --tags") 97 | end 98 | 99 | desc "Update build number, set tag and push version bump commit to the develop branch if needed" 100 | desc "Parameters: 101 | - type: Type of the build which will be set with the tag. For Example: for release candidate builds with tag `1.0.0-rc10` you need to pass `rc`. 102 | - skip_push: Pass **true** if you need to skip push commits to the repository. The default is **false** 103 | " 104 | lane :prepare_for_beta do |options| 105 | current_version = get_version_number_from_plist(target: prod_scheme) 106 | type = options[:type] 107 | tag_prefix = "v" + current_version + "-" + type 108 | 109 | current_build_number = get_build_number_from_plist(target: prod_scheme) 110 | result_tag_index = current_build_number.to_i + 1 111 | build_number = tag_prefix + result_tag_index.to_s 112 | 113 | prepare_for_integration( 114 | version_number: current_version, 115 | type: options[:type], 116 | type_v: result_tag_index.to_s, 117 | build_number: build_number 118 | ) 119 | end 120 | 121 | desc "Set `rc` tag and push to the current branch" 122 | desc "Parameters: 123 | - include_beta: Pass **true** to call `prepare_for_beta` lane for debug and prod types 124 | - skip_push: Pass **true** if you need to skip push commits to the repository. The default is **false** 125 | " 126 | lane :prepare_for_release do |options| 127 | include_beta = options[:include_beta] 128 | if include_beta.nil? || include_beta.to_s == "" 129 | throw "Required include_beta parameter" 130 | end 131 | 132 | if include_beta.to_s == "true" 133 | prepare_for_beta type:"debug" 134 | end 135 | 136 | tag_label = get_version_number_from_plist(target: prod_target) 137 | 138 | type = "rc" 139 | last_tag_index = get_build_number_from_plist(target: prod_target) 140 | 141 | if not type.nil? 142 | tag_label = tag_label + "-" + type + "" + last_tag_index 143 | end 144 | 145 | add_git_tag( 146 | tag: tag_label 147 | ) 148 | 149 | skip_push_opt = options[:skip_push] 150 | needs_to_push = skip_push_opt.to_s.empty? || skip_push_opt.to_s == "false" 151 | 152 | if needs_to_push 153 | sh("git push", error_callback: ->(result) { 154 | UI.error "git push failed with result: #{result}" 155 | }) 156 | sh("git push origin --tags") 157 | end 158 | end 159 | 160 | desc "Upload a new Beta Build to Fabric" 161 | desc "Parameters: 162 | - destination: Type of the build which will be submitted 163 | " 164 | lane :beta do 165 | cocoapods(use_bundle_exec:true) 166 | gym( 167 | scheme: prod_scheme, 168 | configuration: "Debug", 169 | clean: true, 170 | include_bitcode: false, 171 | export_options: { 172 | compileBitcode: false 173 | }, 174 | export_method: "development", 175 | xcargs: "-allowProvisioningUpdates" 176 | ) 177 | upload_to_fabric 178 | refresh_dsyms() 179 | end 180 | 181 | desc "Upload a new Release Build to iTunesConnect" 182 | lane :release do 183 | cocoapods(use_bundle_exec: true) 184 | 185 | cert(development: false) 186 | 187 | sigh( 188 | development: true, 189 | app_identifier: bundle_name 190 | ) 191 | 192 | gym( 193 | scheme: prod_scheme, 194 | configuration: "Release", 195 | clean: true, 196 | include_bitcode: false, 197 | export_options: { 198 | compileBitcode: false 199 | }, 200 | export_method: "app-store" 201 | ) 202 | 203 | upload_to_app_store( 204 | app_identifier: bundle_name 205 | ) 206 | 207 | refresh_dsyms() 208 | end 209 | 210 | desc "Upload a new build to the Fabric" 211 | lane :upload_to_fabric do 212 | # Upload to Fabric 213 | crashlytics( 214 | api_token: "PUT TOKEN HERE", 215 | build_secret: "PUT BUILD SECRET HERE", 216 | emails: ["PUT_EMAILS_HERE"], 217 | notes: ENV[prod_scheme + "_iOS_Crashlytics_Changelog"] 218 | ) 219 | end 220 | 221 | 222 | desc "Send dSYMS to Fabric" 223 | lane :refresh_dsyms do |options| 224 | download_dsyms(app_identifier: bundle_name, version: 'latest') # Download dSYM files from iTC 225 | upload_symbols_to_crashlytics # Upload them to Crashlytics 226 | clean_build_artifacts # Delete the local dSYM files 227 | end 228 | 229 | end 230 | 231 | 232 | # More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md 233 | # All available actions: https://docs.fastlane.tools/actions 234 | 235 | # fastlane reports which actions are used. No personal data is recorded. 236 | # Learn more at https://github.com/fastlane/fastlane#metrics -------------------------------------------------------------------------------- /Example/fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-git_tags' 6 | gem 'fastlane-plugin-versioning' 7 | -------------------------------------------------------------------------------- /Example/fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios build 20 | ``` 21 | fastlane ios build 22 | ``` 23 | 24 | ### ios prepare_for_integration 25 | ``` 26 | fastlane ios prepare_for_integration 27 | ``` 28 | 29 | ### ios prepare_for_beta 30 | ``` 31 | fastlane ios prepare_for_beta 32 | ``` 33 | Set build number and push version bump commit to develop branch with tag 34 | ### ios prepare_for_release 35 | ``` 36 | fastlane ios prepare_for_release 37 | ``` 38 | Set version and build numbers and push version bump commit to master branch with tag 39 | ### ios beta 40 | ``` 41 | fastlane ios beta 42 | ``` 43 | Submit a new Beta Build to Fabric 44 | ### ios release 45 | ``` 46 | fastlane ios release 47 | ``` 48 | Submit a new Release Build to Fabric 49 | ### ios upload_to_fabric 50 | ``` 51 | fastlane ios upload_to_fabric 52 | ``` 53 | 54 | 55 | ---- 56 | 57 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 58 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 59 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 60 | -------------------------------------------------------------------------------- /Example/swiftgen.yml: -------------------------------------------------------------------------------- 1 | xcassets: 2 | inputs: 3 | - OTPTextFieldExample/Resources/Images/Assets.xcassets 4 | outputs: 5 | - templateName: swift4 6 | output: OTPTextFieldExample/Resources/Images/Assets.swift 7 | 8 | fonts: 9 | inputs: 10 | - OTPTextFieldExample/Resources/Fonts/ 11 | outputs: 12 | - templateName: swift4 13 | output: OTPTextFieldExample/Resources/Fonts/Fonts.swift 14 | 15 | strings: 16 | inputs: 17 | - OTPTextFieldExample/Resources/Strings/Localizable.strings 18 | outputs: 19 | - templateName: structured-swift4 20 | output: OTPTextFieldExample/Resources/Strings/Strings.swift 21 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Ensure github repositories are fetched using HTTPS 4 | git_source(:github) do |repo_name| 5 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") 6 | puts(repo_name) 7 | "https://github.com/#{repo_name}.git" 8 | end if Gem::Version.new(Bundler::VERSION) < Gem::Version.new('2') 9 | 10 | gem 'cocoapods', "1.7.3" 11 | gem 'cocoapods-trunk', '1.3.1' -------------------------------------------------------------------------------- /Images/exampleVideo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fixique/OTPTextField/ffc2da9d949f36ff780511f8583d5e22ff2443a3/Images/exampleVideo.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vlad Krupenko 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## Initialization of the working environment. 2 | init: 3 | # Install bundler if not installed 4 | if ! gem spec bundler > /dev/null 2>&1; then\ 5 | echo "bundler gem is not installed!";\ 6 | -sudo gem install bundler;\ 7 | fi 8 | -bundle install --path .bundle 9 | -bundle exec pod repo update 10 | -bundle exec pod install 11 | 12 | ## Allows you to perfrom swiftlint lint command. 13 | lint: 14 | ./Pods/SwiftLint/swiftlint lint --config .swiftlint.yml 15 | 16 | ## Allows you to perfrom swiftlint autocorrect command. 17 | format: 18 | ./Pods/SwiftLint/swiftlint autocorrect --config .swiftlint.yml 19 | 20 | ## Allows you to perform pod install command via bundler settings. Use it instead plain pod install command. 21 | pod: 22 | bundle exec pod install -------------------------------------------------------------------------------- /OTPTextField.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OTPTextField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OTPTextField.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /OTPTextField.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OTPTextField/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /OTPTextField/Library/BaseClasses/BaseInputView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright © 2019 Surf. All rights reserved. 3 | // 4 | // swiftlint:disable unused_setter_value 5 | 6 | import UIKit 7 | 8 | /// This base class implement two protocols for custom text input view 9 | /// It is necessary for implementation sms OTP Code auto fill and other abilities 10 | /// Every method from this class should be overrided by child class 11 | public class BaseInputView: UIView, UITextInput { 12 | 13 | // MARK: - UIKeyInput 14 | 15 | public var hasText: Bool { 16 | return false 17 | } 18 | 19 | public func insertText(_ text: String) { 20 | } 21 | 22 | public func deleteBackward() { 23 | } 24 | 25 | // MARK: - UITextInput 26 | 27 | public var selectedTextRange: UITextRange? { 28 | get { 29 | return nil 30 | } 31 | set(selectedTextRange) { 32 | } 33 | } 34 | 35 | public var markedTextRange: UITextRange? { 36 | return nil 37 | } 38 | 39 | public var markedTextStyle: [NSAttributedString.Key: Any]? { 40 | get { 41 | return nil 42 | } 43 | set(markedTextStyle) { 44 | } 45 | } 46 | 47 | public var beginningOfDocument: UITextPosition { 48 | return UITextPosition() 49 | } 50 | 51 | public var endOfDocument: UITextPosition { 52 | return UITextPosition() 53 | } 54 | 55 | public var inputDelegate: UITextInputDelegate? { 56 | get { 57 | return nil 58 | } 59 | set(inputDelegate) { 60 | } 61 | } 62 | 63 | public var tokenizer: UITextInputTokenizer { 64 | return self 65 | } 66 | 67 | public func replace(_ range: UITextRange, withText text: String) { 68 | } 69 | 70 | public func setMarkedText(_ markedText: String?, selectedRange: NSRange) { 71 | } 72 | 73 | public func unmarkText() { 74 | } 75 | 76 | public func textRange(from fromPosition: UITextPosition, to toPosition: UITextPosition) -> UITextRange? { 77 | return nil 78 | } 79 | 80 | public func position(from position: UITextPosition, offset: Int) -> UITextPosition? { 81 | return nil 82 | } 83 | 84 | public func position(from position: UITextPosition, in direction: UITextLayoutDirection, offset: Int) -> UITextPosition? { 85 | return nil 86 | } 87 | 88 | public func compare(_ position: UITextPosition, to other: UITextPosition) -> ComparisonResult { 89 | return .orderedAscending 90 | } 91 | 92 | public func offset(from: UITextPosition, to toPosition: UITextPosition) -> Int { 93 | return 0 94 | } 95 | 96 | public func position(within range: UITextRange, farthestIn direction: UITextLayoutDirection) -> UITextPosition? { 97 | return nil 98 | } 99 | 100 | public func characterRange(byExtending position: UITextPosition, in direction: UITextLayoutDirection) -> UITextRange? { 101 | return nil 102 | } 103 | 104 | public func baseWritingDirection(for position: UITextPosition, in direction: UITextStorageDirection) -> NSWritingDirection { 105 | return .leftToRight 106 | } 107 | 108 | public func setBaseWritingDirection(_ writingDirection: NSWritingDirection, for range: UITextRange) { 109 | } 110 | 111 | public func firstRect(for range: UITextRange) -> CGRect { 112 | return .zero 113 | } 114 | 115 | public func caretRect(for position: UITextPosition) -> CGRect { 116 | return .zero 117 | } 118 | 119 | public func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { 120 | return [] 121 | } 122 | 123 | public func closestPosition(to point: CGPoint) -> UITextPosition? { 124 | return nil 125 | } 126 | 127 | public func closestPosition(to point: CGPoint, within range: UITextRange) -> UITextPosition? { 128 | return nil 129 | } 130 | 131 | public func characterRange(at point: CGPoint) -> UITextRange? { 132 | return nil 133 | } 134 | 135 | public func text(in range: UITextRange) -> String? { 136 | return nil 137 | } 138 | 139 | } 140 | 141 | // MARK: - UITextInputTokenizer 142 | 143 | extension BaseInputView: UITextInputTokenizer { 144 | 145 | public func rangeEnclosingPosition(_ position: UITextPosition, with granularity: UITextGranularity, inDirection direction: UITextDirection) -> UITextRange? { 146 | return nil 147 | } 148 | 149 | public func isPosition(_ position: UITextPosition, atBoundary granularity: UITextGranularity, inDirection direction: UITextDirection) -> Bool { 150 | return true 151 | } 152 | 153 | public func position(from position: UITextPosition, toBoundary granularity: UITextGranularity, inDirection direction: UITextDirection) -> UITextPosition? { 154 | return nil 155 | } 156 | 157 | public func isPosition(_ position: UITextPosition, withinTextUnit granularity: UITextGranularity, inDirection direction: UITextDirection) -> Bool { 158 | return true 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /OTPTextField/Library/Extensions/Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | extension Array { 10 | 11 | subscript (safe index: Int) -> Element? { 12 | return indices ~= index ? self[index] : nil 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /OTPTextField/Library/Extensions/UIView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView.swift 3 | // OTPTextField 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | static func loadFromNib() -> T { 14 | let bundle = Bundle(for: self) 15 | let nibName = String(describing: self) 16 | let nib = UINib(nibName: nibName, bundle: bundle) 17 | 18 | guard let view = nib.instantiate(withOwner: self, options: nil).first as? T else { 19 | return T() 20 | } 21 | 22 | return view 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /OTPTextField/Library/Protocols/OTPTextFieldData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPTextFieldData.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Protocol for create OTPTextField custom data adapter 12 | public protocol OTPTextFieldData: class { 13 | /// Should return number of pins 14 | func numberOfPins() -> Int 15 | /// Should return view for current pin at index 16 | func otpTextField(viewAt index: Int) -> PinContainer 17 | /// Should return size for pin at index 18 | func otpTextField(sizeForViewAt index: Int) -> CGSize 19 | /// Should return space between pins 20 | func otpTextField(spaceForViewAt index: Int) -> CGFloat 21 | } 22 | -------------------------------------------------------------------------------- /OTPTextField/Library/Protocols/PinContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinContainer.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Protocol for create custom pin view and use it with OTPTextField adapater 12 | public protocol PinContainer: class { 13 | /// Property return instance of current view 14 | var view: UIView { get } 15 | /// Method for set character value for view 16 | func set(value: String?) 17 | /// Remove characters from view 18 | func clear() 19 | /// Method for pin states update. 20 | /// 21 | /// Each pin has two states: active or inactive, error state or not. 22 | /// When implementing a pin, everyone have to decide for himself: 23 | /// what is more important - an error state or an active state? 24 | /// Depending on the answer to this question, implement the display logic. 25 | /// And in order not to store the state inside the view - 26 | /// this method returns the current state of the object for both states. 27 | /// - Parameters: 28 | /// - isActive: true if target pin is active, false if not 29 | /// - isError: true if all OTP-filed has error-state in this moment, false if not 30 | func setupState(isActive: Bool, isError: Bool) 31 | } 32 | 33 | public extension PinContainer where Self: UIView { 34 | 35 | var view: UIView { 36 | return self 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /OTPTextField/OTPTextField.h: -------------------------------------------------------------------------------- 1 | // 2 | // OTPTextField.h 3 | // OTPTextField 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for OTPTextField. 12 | FOUNDATION_EXPORT double OTPTextFieldVersionNumber; 13 | 14 | //! Project version string for OTPTextField. 15 | FOUNDATION_EXPORT const unsigned char OTPTextFieldVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Configuration/OTPFieldConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPFieldConfiguration.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct OTPFieldConfiguration { 12 | 13 | // MARK: - Public Properties 14 | 15 | public let adapter: OTPTextFieldData 16 | public let keyboardType: UIKeyboardType 17 | public let keyboardAppearance: UIKeyboardAppearance 18 | public let autocorrectionType: UITextAutocorrectionType 19 | public let allowedCharactersSet: CharacterSet 20 | 21 | // MARK: - Initialization 22 | 23 | public init(adapter: OTPTextFieldData, 24 | keyboardType: UIKeyboardType = .numberPad, 25 | keyboardAppearance: UIKeyboardAppearance = .light, 26 | autocorrectionType: UITextAutocorrectionType = .no, 27 | allowedCharactersSet: CharacterSet = .alphanumerics) { 28 | self.adapter = adapter 29 | self.keyboardType = keyboardType 30 | self.keyboardAppearance = keyboardAppearance 31 | self.autocorrectionType = autocorrectionType 32 | self.allowedCharactersSet = allowedCharactersSet 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Default/Adapter/DefaultTextFieldAdapter/DefaultTextFieldAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultTextFieldAdapter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class DefaultTextFieldAdapter: NSObject { 12 | 13 | // MARK: - Constants 14 | 15 | private enum Constants { 16 | static let numberOfPins = 4 17 | static let pinSize = CGSize(width: 56.0, height: 48.0) 18 | static let space: CGFloat = 16.0 19 | } 20 | 21 | // MARK: - Initialization 22 | 23 | override public init() {} 24 | 25 | } 26 | 27 | // MARK: - OTPTextFieldData 28 | 29 | extension DefaultTextFieldAdapter: OTPTextFieldData { 30 | 31 | public func numberOfPins() -> Int { 32 | return Constants.numberOfPins 33 | } 34 | 35 | public func otpTextField(viewAt index: Int) -> PinContainer { 36 | guard let pinView = DefaultPinView.loadFromNib() as? PinContainer else { 37 | fatalError("Can't find class for init pinField") 38 | } 39 | return pinView 40 | } 41 | 42 | public func otpTextField(sizeForViewAt index: Int) -> CGSize { 43 | return Constants.pinSize 44 | } 45 | 46 | public func otpTextField(spaceForViewAt index: Int) -> CGFloat { 47 | return Constants.space 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Default/Adapter/RoundTextFieldAdapter/RoundTextFieldAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundTextFieldAdapter.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class RoundTextFieldAdapter: NSObject { 12 | 13 | // MARK: - Constants 14 | 15 | private enum Constants { 16 | static let numberOfPins = 6 17 | static let pinSize = CGSize(width: 44, height: 44) 18 | static let space: CGFloat = 6 19 | } 20 | 21 | // MARK: - Inititalization 22 | 23 | override public init() {} 24 | 25 | } 26 | 27 | // MARK: - OTPTextFieldData 28 | 29 | extension RoundTextFieldAdapter: OTPTextFieldData { 30 | 31 | public func numberOfPins() -> Int { 32 | return Constants.numberOfPins 33 | } 34 | 35 | public func otpTextField(viewAt index: Int) -> PinContainer { 36 | guard let pinView = RoundPinView.loadFromNib() as? PinContainer else { 37 | fatalError("Can't find class for init pinField") 38 | } 39 | return pinView 40 | } 41 | 42 | public func otpTextField(sizeForViewAt index: Int) -> CGSize { 43 | return Constants.pinSize 44 | } 45 | 46 | public func otpTextField(spaceForViewAt index: Int) -> CGFloat { 47 | return Constants.space 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Default/PinViews/DefaultPinView/DefaultPinView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultPinView.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public final class DefaultPinView: UIView { 12 | 13 | // MARK: - IBOutlets 14 | 15 | @IBOutlet private weak var containerView: UIView! 16 | @IBOutlet private weak var codeLabel: UILabel! 17 | @IBOutlet private weak var indicatorView: UIView! 18 | 19 | // MARK: - UIView 20 | 21 | override public func awakeFromNib() { 22 | super.awakeFromNib() 23 | setupInitialState() 24 | } 25 | 26 | } 27 | 28 | // MARK: - Pin Container 29 | 30 | extension DefaultPinView: PinContainer { 31 | 32 | public func set(value: String?) { 33 | codeLabel.text = value 34 | } 35 | 36 | public func clear() { 37 | codeLabel.text = nil 38 | } 39 | 40 | public func setupState(isActive: Bool, isError: Bool) { 41 | if isActive && indicatorView.isHidden { 42 | startIndicatorAnimation() 43 | } else if !isActive { 44 | stopIndicatorAnimation() 45 | } 46 | } 47 | 48 | } 49 | 50 | // MARK: - Configuration 51 | 52 | private extension DefaultPinView { 53 | 54 | func setupInitialState() { 55 | configureContainerView() 56 | configureCodeLabel() 57 | configureIndicatorView() 58 | } 59 | 60 | func configureContainerView() { 61 | containerView.backgroundColor = UIColor(red: 246 / 255, green: 246 / 255, blue: 246 / 255, alpha: 1.0) 62 | containerView.layer.cornerRadius = 2.0 63 | containerView.layer.masksToBounds = true 64 | } 65 | 66 | func configureCodeLabel() { 67 | codeLabel.font = UIFont.systemFont(ofSize: 20, weight: .medium) 68 | codeLabel.textColor = UIColor.black 69 | codeLabel.textAlignment = .center 70 | codeLabel.text = nil 71 | } 72 | 73 | func configureIndicatorView() { 74 | indicatorView.backgroundColor = UIColor(red: 212 / 255, green: 0, blue: 0, alpha: 1.0) 75 | indicatorView.isHidden = true 76 | } 77 | 78 | } 79 | 80 | // MARK: - Animation 81 | 82 | private extension DefaultPinView { 83 | 84 | func startIndicatorAnimation() { 85 | let appearAnimation = CABasicAnimation(keyPath: "opacity") 86 | appearAnimation.fromValue = 0.0 87 | appearAnimation.toValue = 1.0 88 | appearAnimation.duration = 0.5 89 | appearAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 90 | 91 | let disappearAnimation = CABasicAnimation(keyPath: "opacity") 92 | disappearAnimation.fromValue = 1.0 93 | disappearAnimation.toValue = 0.0 94 | disappearAnimation.duration = 0.5 95 | disappearAnimation.timingFunction = CAMediaTimingFunction(name: .easeIn) 96 | 97 | let animationGroup = CAAnimationGroup() 98 | animationGroup.animations = [appearAnimation, disappearAnimation] 99 | animationGroup.duration = 1 100 | animationGroup.repeatCount = .infinity 101 | 102 | indicatorView.isHidden = false 103 | indicatorView.layer.add(animationGroup, forKey: "fade") 104 | } 105 | 106 | func stopIndicatorAnimation() { 107 | indicatorView.isHidden = true 108 | indicatorView.layer.removeAllAnimations() 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Default/PinViews/DefaultPinView/DefaultPinView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Default/PinViews/RoundPinView/RoundPinView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundPinView.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class RoundPinView: UIView { 12 | 13 | // MARK: - Constants 14 | 15 | private enum Constants { 16 | static let errorColor = UIColor(red: 212 / 255, green: 0, blue: 0, alpha: 1.0) 17 | static let defaultColor = UIColor(red: 221 / 255, green: 221 / 255, blue: 221 / 255, alpha: 1.0) 18 | static let indicatorColor = UIColor(red: 50 / 255, green: 0, blue: 156 / 255, alpha: 1.0) 19 | } 20 | 21 | // MARK: - IBOutlets 22 | 23 | @IBOutlet private weak var outerContainerView: UIView! 24 | @IBOutlet private weak var innerContainerView: UIView! 25 | @IBOutlet private weak var codeLabel: UILabel! 26 | 27 | // MARK: - UIView 28 | 29 | override func awakeFromNib() { 30 | super.awakeFromNib() 31 | setupInitilState() 32 | } 33 | 34 | } 35 | 36 | // MARK: - Pin Container 37 | 38 | extension RoundPinView: PinContainer { 39 | 40 | public func set(value: String?) { 41 | codeLabel.text = value 42 | } 43 | 44 | public func clear() { 45 | codeLabel.text = nil 46 | } 47 | 48 | public func setupState(isActive: Bool, isError: Bool) { 49 | if isActive { 50 | outerContainerView.backgroundColor = Constants.indicatorColor 51 | } else { 52 | let color = isError ? Constants.errorColor : Constants.defaultColor 53 | outerContainerView.backgroundColor = color 54 | } 55 | } 56 | 57 | } 58 | 59 | // MARK: - Configuration 60 | 61 | private extension RoundPinView { 62 | 63 | func setupInitilState() { 64 | configureContainerViews() 65 | configureCodeLabel() 66 | } 67 | 68 | func configureContainerViews() { 69 | outerContainerView.backgroundColor = Constants.defaultColor 70 | outerContainerView.layer.cornerRadius = outerContainerView.frame.height / 2 71 | outerContainerView.layer.masksToBounds = true 72 | 73 | innerContainerView.backgroundColor = UIColor(red: 242 / 255, green: 242 / 255, blue: 242 / 255, alpha: 1.0) 74 | innerContainerView.layer.cornerRadius = innerContainerView.frame.height / 2 75 | innerContainerView.layer.masksToBounds = true 76 | } 77 | 78 | func configureCodeLabel() { 79 | codeLabel.textColor = UIColor(red: 34 / 255, green: 34 / 255, blue: 34 / 255, alpha: 1.0) 80 | codeLabel.font = UIFont.systemFont(ofSize: 14.0, weight: .regular) 81 | codeLabel.textAlignment = .center 82 | codeLabel.text = nil 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /OTPTextField/TextField/Default/PinViews/RoundPinView/RoundPinView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /OTPTextField/TextField/OTPTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPTextField.swift 3 | // OTPTextFieldExample 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Class for create custom OTPTextField with ability for enter otp characters and callback when otp enter 12 | public final class OTPTextField: BaseInputView { 13 | 14 | // MARK: - Public Properties 15 | 16 | public var onBeginEditing: (() -> Void)? 17 | public var onTextChanged: ((String?) -> Void)? 18 | public var onEndEditing: (() -> Void)? 19 | public var onOTPEnter: ((String) -> Void)? 20 | 21 | override public var inputView: UIView? { 22 | get { return innerInputView } 23 | set { innerInputView = newValue } 24 | } 25 | 26 | override public var inputAccessoryView: UIView? { 27 | get { return innerInputAccessoryView } 28 | set { innerInputAccessoryView = newValue } 29 | } 30 | 31 | override public var canBecomeFirstResponder: Bool { 32 | return true 33 | } 34 | 35 | // MARK: - Private Properties 36 | 37 | private var configuration: OTPFieldConfiguration = OTPFieldConfiguration(adapter: DefaultTextFieldAdapter()) { 38 | didSet { 39 | updateUI() 40 | } 41 | } 42 | private var keyboardType: UIKeyboardType = .numberPad 43 | private var keyboardAppearance: UIKeyboardAppearance = .light 44 | private var autocorrectionType: UITextAutocorrectionType = .no 45 | private var allowedCharactersSet: CharacterSet = .alphanumerics 46 | private var isInputEnabled: Bool = true 47 | 48 | private var innerInputView: UIView? 49 | private var innerInputAccessoryView: UIView? 50 | private var charactersCount: Int { 51 | return configuration.adapter.numberOfPins() 52 | } 53 | private var pinViews: [PinContainer] = [] 54 | private var text: String? 55 | private var currentCharactersCount: Int { 56 | return text?.count ?? 0 57 | } 58 | private var isError = false 59 | 60 | // MARK: - Initialization 61 | 62 | override public init(frame: CGRect) { 63 | super.init(frame: frame) 64 | updateUI() 65 | } 66 | 67 | required init?(coder: NSCoder) { 68 | super.init(coder: coder) 69 | updateUI() 70 | } 71 | 72 | // MARK: - UIView 73 | 74 | override public func awakeFromNib() { 75 | super.awakeFromNib() 76 | updateUI() 77 | } 78 | 79 | override public func layoutSubviews() { 80 | super.layoutSubviews() 81 | configureInputPinViews() 82 | } 83 | 84 | @discardableResult 85 | override public func becomeFirstResponder() -> Bool { 86 | onBeginEditing?() 87 | manageIndicatorOnBecomeResponder() 88 | return super.becomeFirstResponder() 89 | } 90 | 91 | @discardableResult 92 | override public func resignFirstResponder() -> Bool { 93 | onEndEditing?() 94 | pinViews.forEach { $0.setupState(isActive: false, isError: isError) } 95 | return super.resignFirstResponder() 96 | } 97 | 98 | // MARK: - BaseInputView 99 | 100 | override public var hasText: Bool { 101 | return text?.isEmpty == false 102 | } 103 | 104 | override public func insertText(_ character: String) { 105 | insertCharacters(character) 106 | } 107 | 108 | override public func deleteBackward() { 109 | guard hasText, isInputEnabled else { 110 | return 111 | } 112 | pinViews[safe: currentCharactersCount - 1]?.clear() 113 | pinViews[safe: currentCharactersCount - 1]?.setupState(isActive: true, isError: isError) 114 | pinViews[safe: currentCharactersCount]?.setupState(isActive: false, isError: isError) 115 | text?.removeLast() 116 | notifyIfTextChanged() 117 | } 118 | 119 | // MARK: - Touches 120 | 121 | override public func touchesEnded(_ touches: Set, with event: UIEvent?) { 122 | guard let touch = touches.first else { 123 | return 124 | } 125 | let location = touch.location(in: self) 126 | guard bounds.contains(location) else { 127 | return 128 | } 129 | becomeFirstResponder() 130 | } 131 | 132 | // MARK: - Public Methods 133 | 134 | /// Method will set configuration for field and update field appearance 135 | public func setConfiguration(_ configuration: OTPFieldConfiguration) { 136 | self.configuration = configuration 137 | } 138 | 139 | /// Method will disable input by keyboard and setText method 140 | public func setEnabled(_ enabled: Bool) { 141 | isInputEnabled = enabled 142 | } 143 | 144 | /// Method will set text by one character per pin view 145 | public func setText(_ text: String) { 146 | insertCharacters(text) 147 | } 148 | 149 | /// Method will affect all pin views and set error state 150 | public func setError() { 151 | isError = true 152 | let activeIndex = isFirstResponder ? currentCharactersCount : nil 153 | for (index, pin) in pinViews.enumerated() { 154 | pin.setupState(isActive: index == activeIndex, isError: isError) 155 | } 156 | } 157 | 158 | /// Method will affect all pin views and remove state 159 | public func removeError() { 160 | guard isError else { 161 | return 162 | } 163 | isError = false 164 | let activeIndex = isFirstResponder ? currentCharactersCount : nil 165 | for (index, pin) in pinViews.enumerated() { 166 | pin.setupState(isActive: index == activeIndex, isError: isError) 167 | } 168 | } 169 | 170 | /// Method will clear text field 171 | public func clear() { 172 | text = nil 173 | notifyIfTextChanged() 174 | updateUI() 175 | } 176 | 177 | } 178 | 179 | // MARK: - Configuration 180 | 181 | private extension OTPTextField { 182 | 183 | /// Method rebuild all pinField views 184 | func updateUI() { 185 | applyConfiguraion() 186 | loadPinViews() 187 | configureInputPinViews() 188 | } 189 | 190 | /// Method apply current configuration for viewl 191 | func applyConfiguraion() { 192 | keyboardType = configuration.keyboardType 193 | keyboardAppearance = configuration.keyboardAppearance 194 | autocorrectionType = configuration.autocorrectionType 195 | allowedCharactersSet = configuration.allowedCharactersSet 196 | } 197 | 198 | /// Method remove from superview old instance of pinviews and reload all pins with new configuration 199 | func loadPinViews() { 200 | pinViews.forEach { $0.view.removeFromSuperview() } 201 | pinViews.removeAll() 202 | for index in 0.. Bool { 227 | let newText = text.map { $0 + character } ?? character 228 | let isCharacterMatchingSet = character.trimmingCharacters(in: allowedCharactersSet).isEmpty 229 | let isCorrectLength = newText.count <= charactersCount 230 | return !character.isEmpty && isCharacterMatchingSet && isCorrectLength && isInputEnabled 231 | } 232 | 233 | /// Method check if we can insert characters 234 | /// if more than one character will fill all pins 235 | /// if one character will set in first empty pin view 236 | func insertCharacters(_ characters: String) { 237 | guard canInsertCharacter(characters) else { 238 | return 239 | } 240 | if characters.count > 1 { 241 | pinViews.forEach { $0.setupState(isActive: false, isError: isError) } 242 | for (index, char) in characters.enumerated() { 243 | pinViews[safe: index]?.set(value: String(char)) 244 | } 245 | text = characters 246 | } else { 247 | text = text.map { $0 + characters } ?? characters 248 | pinViews[safe: currentCharactersCount - 1]?.setupState(isActive: false, isError: isError) 249 | pinViews[safe: currentCharactersCount - 1]?.set(value: characters) 250 | pinViews[safe: currentCharactersCount]?.setupState(isActive: true, isError: isError) 251 | } 252 | notifyIfTextChanged() 253 | } 254 | 255 | /// Method will triggered if all pins filled 256 | func notifyIfTextChanged() { 257 | onTextChanged?(text) 258 | guard currentCharactersCount == charactersCount else { 259 | return 260 | } 261 | onOTPEnter?(text ?? "") 262 | } 263 | 264 | /// Method will animate indicator on current empty pin view 265 | func manageIndicatorOnBecomeResponder() { 266 | guard let text = text else { 267 | pinViews[safe: 0]?.setupState(isActive: true, isError: isError) 268 | return 269 | } 270 | pinViews[safe: text.count]?.setupState(isActive: true, isError: isError) 271 | } 272 | 273 | } 274 | -------------------------------------------------------------------------------- /OTPTextFieldTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /OTPTextFieldTests/OTPTextFieldTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPTextFieldTests.swift 3 | // OTPTextFieldTests 4 | // 5 | // Created by Vladislav Krupenko on 19.03.2020. 6 | // Copyright © 2020 Fixique. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import OTPTextField 11 | 12 | class OTPTextFieldTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11.0' 2 | 3 | def utils 4 | pod 'SwiftLint', '0.30.1' 5 | end 6 | 7 | def common_pods 8 | utils 9 | end 10 | 11 | target 'OTPTextField' do 12 | use_frameworks! 13 | common_pods 14 | 15 | target 'OTPTextFieldTests' do 16 | inherit! :search_paths 17 | common_pods 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OTPTextField 2 | 3 | [![Build Status](https://travis-ci.com/fixique/OTPTextField.svg?branch=master)](https://travis-ci.com/fixique/OTPTextField) [![codebeat badge](https://codebeat.co/badges/b5135007-19cb-45e1-80cc-aa80b8ccd9f8)](https://codebeat.co/projects/github-com-fixique-otptextfield-master) ![Swift Version](https://img.shields.io/badge/swift-5.0-orange) ![LISENCE](https://img.shields.io/badge/LICENSE-MIT-green) 4 | 5 | ## Overview 6 | 7 | This library provides the ability to quickly implement the SMS input field. By default it supports the ability to automatically insert OneTimeCode into the field. 8 | 9 | **Features:** 10 | 11 | 1. Two default input fields 12 | 2. The ability to make your own design 13 | 3. Ability to animate an input indicator 14 | 4. Ability to handle error state 15 | 16 |

17 | 18 |

19 | 20 | ## Installation 21 | 22 | #### CocoaPods 23 | 24 | [CocoaPods](https://cocoapods.org/) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate OTPTextField into your Xcode project using CocoaPods, specify it in your `Podfile`: 25 | 26 | ```ruby 27 | pod 'SFOTPTextField', '~> 1.0.0' 28 | ``` 29 | 30 | 31 | 32 | ## Usage 33 | 34 | #### Default Usage 35 | 36 | In order to add a default field: 37 | 38 | 1. Create instance of OTPTextField 39 | 2. Add to your ViewController 40 | 3. Implement handlers 41 | 42 | #### Custom Usage 43 | 44 | In order to implement cutom field: 45 | 46 | 1. Create your own pinView and implement protocol `PinContainer` 47 | 2. Create your own adapter class and implement protocol `OTPTextFieldData` 48 | 3. Add instance of OTPTextField to your ViewController 49 | 4. Set your own custom configuration and implement handlers 50 | 51 | ```swift 52 | func configureOtpField() { 53 | let configuration = OTPFieldConfiguration(adapter: CustomFieldAdapter(), 54 | keyboardType: .namePhonePad, 55 | keyboardAppearance: .light, 56 | autocorrectionType: .no, 57 | allowedCharactersSet: .alphanumerics) 58 | otpField.setConfiguration(configuration) 59 | otpField.onBeginEditing = { 60 | print("Handle Begin Editing") 61 | } 62 | otpField.onEndEditing = { 63 | print("Handle End Editing") 64 | } 65 | otpField.onOTPEnter = { code in 66 | print("Handle OTP entered action") 67 | } 68 | otpField.onTextChanged = { code in 69 | print("Handle code changing") 70 | } 71 | } 72 | ``` 73 | 74 | ## License 75 | 76 | OTPTextField is released under the MIT license. [See LICENSE](https://github.com/fixique/OTPTextField/blob/master/LICENSE) for details. 77 | 78 | -------------------------------------------------------------------------------- /SFOTPTextField.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "SFOTPTextField" 4 | s.version = "1.1.1" 5 | s.summary = "This library makes it easy to implement SMS input field with the ability to automatically substitute code from SMS messages" 6 | s.homepage = "https://github.com/fixique/OTPTextField" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | 9 | s.author = { "Krupenko Vliadislav" => "dev.fixique@gmail.com" } 10 | s.ios.deployment_target = "11.0" 11 | s.swift_version = '5.0' 12 | 13 | s.source = { :git => "https://github.com/fixique/OTPTextField.git", :tag => "#{s.version}" } 14 | s.source_files = 'OTPTextField/**/*.{swift,xib}' 15 | 16 | 17 | s.framework = "UIKit" 18 | 19 | end 20 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman --------------------------------------------------------------------------------