├── LICENSE ├── OTPFieldView.podspec ├── OTPFieldView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── vaibhavbhasin.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── vaibhavbhasin.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── OTPFieldView ├── .DS_Store ├── Info.plist ├── OTPFieldView.h ├── OTPFieldView.swift └── OTPTextField.swift ├── OTPFieldViewTests ├── Info.plist └── OTPFieldViewTests.swift ├── README.md └── Screenshots ├── IMG_0198.PNG ├── IMG_0199.PNG ├── IMG_0200.PNG ├── IMG_0201.PNG └── IMG_0202.PNG /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vaibhav Bhasin 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 | -------------------------------------------------------------------------------- /OTPFieldView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = "OTPFieldView" 4 | spec.version = "1.0.1" 5 | spec.summary = "A CocoaPods library for One Time Password View written in Swift" 6 | 7 | spec.description = <<-DESC 8 | This library helps you create One-Time-Password view for iOS Applications 9 | DESC 10 | 11 | spec.homepage = "https://github.com/Root-vb/OTPFieldView" 12 | spec.license = { :type => "MIT", :file => "LICENSE" } 13 | spec.author = { "Vaibhav Bhasin" => "vaibhavbhasin15@gmail.com" } 14 | 15 | spec.ios.deployment_target = "10.3" 16 | spec.swift_version = "5.0" 17 | 18 | spec.source = { :git => "https://github.com/Root-vb/OTPFieldView.git", :tag => "#{spec.version}" } 19 | spec.source_files = "OTPFieldView/**/*.{h,m,swift}" 20 | 21 | end 22 | -------------------------------------------------------------------------------- /OTPFieldView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 13EDE57623278D8700B45FF6 /* OTPFieldView.h in Headers */ = {isa = PBXBuildFile; fileRef = 13EDE57423278D8700B45FF6 /* OTPFieldView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 13EDE57D23278DC400B45FF6 /* OTPFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE57C23278DC400B45FF6 /* OTPFieldView.swift */; }; 12 | 13EDE57F23278E9A00B45FF6 /* OTPTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE57E23278E9A00B45FF6 /* OTPTextField.swift */; }; 13 | 13EDE5872327A8C400B45FF6 /* OTPFieldViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE5862327A8C400B45FF6 /* OTPFieldViewTests.swift */; }; 14 | 13EDE5892327A8C400B45FF6 /* OTPFieldView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 13EDE58A2327A8C400B45FF6 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 13EDE56823278D8700B45FF6 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 13EDE57023278D8700B45FF6; 23 | remoteInfo = OTPFieldView; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OTPFieldView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 13EDE57423278D8700B45FF6 /* OTPFieldView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTPFieldView.h; sourceTree = ""; }; 30 | 13EDE57523278D8700B45FF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 13EDE57C23278DC400B45FF6 /* OTPFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPFieldView.swift; sourceTree = ""; }; 32 | 13EDE57E23278E9A00B45FF6 /* OTPTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPTextField.swift; sourceTree = ""; }; 33 | 13EDE5842327A8C400B45FF6 /* OTPFieldViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OTPFieldViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 13EDE5862327A8C400B45FF6 /* OTPFieldViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPFieldViewTests.swift; sourceTree = ""; }; 35 | 13EDE5882327A8C400B45FF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 13EDE56E23278D8700B45FF6 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | 13EDE5812327A8C400B45FF6 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | 13EDE5892327A8C400B45FF6 /* OTPFieldView.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 13EDE56723278D8700B45FF6 = { 58 | isa = PBXGroup; 59 | children = ( 60 | 13EDE57323278D8700B45FF6 /* OTPFieldView */, 61 | 13EDE5852327A8C400B45FF6 /* OTPFieldViewTests */, 62 | 13EDE57223278D8700B45FF6 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | 13EDE57223278D8700B45FF6 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */, 70 | 13EDE5842327A8C400B45FF6 /* OTPFieldViewTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 13EDE57323278D8700B45FF6 /* OTPFieldView */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 13EDE57423278D8700B45FF6 /* OTPFieldView.h */, 79 | 13EDE57523278D8700B45FF6 /* Info.plist */, 80 | 13EDE57C23278DC400B45FF6 /* OTPFieldView.swift */, 81 | 13EDE57E23278E9A00B45FF6 /* OTPTextField.swift */, 82 | ); 83 | path = OTPFieldView; 84 | sourceTree = ""; 85 | }; 86 | 13EDE5852327A8C400B45FF6 /* OTPFieldViewTests */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 13EDE5862327A8C400B45FF6 /* OTPFieldViewTests.swift */, 90 | 13EDE5882327A8C400B45FF6 /* Info.plist */, 91 | ); 92 | path = OTPFieldViewTests; 93 | sourceTree = ""; 94 | }; 95 | /* End PBXGroup section */ 96 | 97 | /* Begin PBXHeadersBuildPhase section */ 98 | 13EDE56C23278D8700B45FF6 /* Headers */ = { 99 | isa = PBXHeadersBuildPhase; 100 | buildActionMask = 2147483647; 101 | files = ( 102 | 13EDE57623278D8700B45FF6 /* OTPFieldView.h in Headers */, 103 | ); 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXHeadersBuildPhase section */ 107 | 108 | /* Begin PBXNativeTarget section */ 109 | 13EDE57023278D8700B45FF6 /* OTPFieldView */ = { 110 | isa = PBXNativeTarget; 111 | buildConfigurationList = 13EDE57923278D8700B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldView" */; 112 | buildPhases = ( 113 | 13EDE56C23278D8700B45FF6 /* Headers */, 114 | 13EDE56D23278D8700B45FF6 /* Sources */, 115 | 13EDE56E23278D8700B45FF6 /* Frameworks */, 116 | 13EDE56F23278D8700B45FF6 /* Resources */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = OTPFieldView; 123 | productName = OTPFieldView; 124 | productReference = 13EDE57123278D8700B45FF6 /* OTPFieldView.framework */; 125 | productType = "com.apple.product-type.framework"; 126 | }; 127 | 13EDE5832327A8C400B45FF6 /* OTPFieldViewTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 13EDE58C2327A8C400B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldViewTests" */; 130 | buildPhases = ( 131 | 13EDE5802327A8C400B45FF6 /* Sources */, 132 | 13EDE5812327A8C400B45FF6 /* Frameworks */, 133 | 13EDE5822327A8C400B45FF6 /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | 13EDE58B2327A8C400B45FF6 /* PBXTargetDependency */, 139 | ); 140 | name = OTPFieldViewTests; 141 | productName = OTPFieldViewTests; 142 | productReference = 13EDE5842327A8C400B45FF6 /* OTPFieldViewTests.xctest */; 143 | productType = "com.apple.product-type.bundle.unit-test"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 13EDE56823278D8700B45FF6 /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | LastSwiftUpdateCheck = 1030; 152 | LastUpgradeCheck = 1030; 153 | ORGANIZATIONNAME = "Vaibhav Bhasin"; 154 | TargetAttributes = { 155 | 13EDE57023278D8700B45FF6 = { 156 | CreatedOnToolsVersion = 10.3; 157 | LastSwiftMigration = 1030; 158 | }; 159 | 13EDE5832327A8C400B45FF6 = { 160 | CreatedOnToolsVersion = 10.3; 161 | }; 162 | }; 163 | }; 164 | buildConfigurationList = 13EDE56B23278D8700B45FF6 /* Build configuration list for PBXProject "OTPFieldView" */; 165 | compatibilityVersion = "Xcode 9.3"; 166 | developmentRegion = en; 167 | hasScannedForEncodings = 0; 168 | knownRegions = ( 169 | en, 170 | ); 171 | mainGroup = 13EDE56723278D8700B45FF6; 172 | productRefGroup = 13EDE57223278D8700B45FF6 /* Products */; 173 | projectDirPath = ""; 174 | projectRoot = ""; 175 | targets = ( 176 | 13EDE57023278D8700B45FF6 /* OTPFieldView */, 177 | 13EDE5832327A8C400B45FF6 /* OTPFieldViewTests */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXResourcesBuildPhase section */ 183 | 13EDE56F23278D8700B45FF6 /* Resources */ = { 184 | isa = PBXResourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | 13EDE5822327A8C400B45FF6 /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXSourcesBuildPhase section */ 200 | 13EDE56D23278D8700B45FF6 /* Sources */ = { 201 | isa = PBXSourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 13EDE57D23278DC400B45FF6 /* OTPFieldView.swift in Sources */, 205 | 13EDE57F23278E9A00B45FF6 /* OTPTextField.swift in Sources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | 13EDE5802327A8C400B45FF6 /* Sources */ = { 210 | isa = PBXSourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 13EDE5872327A8C400B45FF6 /* OTPFieldViewTests.swift in Sources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXSourcesBuildPhase section */ 218 | 219 | /* Begin PBXTargetDependency section */ 220 | 13EDE58B2327A8C400B45FF6 /* PBXTargetDependency */ = { 221 | isa = PBXTargetDependency; 222 | target = 13EDE57023278D8700B45FF6 /* OTPFieldView */; 223 | targetProxy = 13EDE58A2327A8C400B45FF6 /* PBXContainerItemProxy */; 224 | }; 225 | /* End PBXTargetDependency section */ 226 | 227 | /* Begin XCBuildConfiguration section */ 228 | 13EDE57723278D8700B45FF6 /* Debug */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | ALWAYS_SEARCH_USER_PATHS = NO; 232 | CLANG_ANALYZER_NONNULL = YES; 233 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 234 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 235 | CLANG_CXX_LIBRARY = "libc++"; 236 | CLANG_ENABLE_MODULES = YES; 237 | CLANG_ENABLE_OBJC_ARC = YES; 238 | CLANG_ENABLE_OBJC_WEAK = YES; 239 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_COMMA = YES; 242 | CLANG_WARN_CONSTANT_CONVERSION = YES; 243 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 244 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 245 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 246 | CLANG_WARN_EMPTY_BODY = YES; 247 | CLANG_WARN_ENUM_CONVERSION = YES; 248 | CLANG_WARN_INFINITE_RECURSION = YES; 249 | CLANG_WARN_INT_CONVERSION = YES; 250 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 252 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 253 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 254 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 255 | CLANG_WARN_STRICT_PROTOTYPES = YES; 256 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 257 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 258 | CLANG_WARN_UNREACHABLE_CODE = YES; 259 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 260 | CODE_SIGN_IDENTITY = "iPhone Developer"; 261 | COPY_PHASE_STRIP = NO; 262 | CURRENT_PROJECT_VERSION = 1; 263 | DEBUG_INFORMATION_FORMAT = dwarf; 264 | ENABLE_STRICT_OBJC_MSGSEND = YES; 265 | ENABLE_TESTABILITY = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu11; 267 | GCC_DYNAMIC_NO_PIC = NO; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_OPTIMIZATION_LEVEL = 0; 270 | GCC_PREPROCESSOR_DEFINITIONS = ( 271 | "DEBUG=1", 272 | "$(inherited)", 273 | ); 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 12.4; 281 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 282 | MTL_FAST_MATH = YES; 283 | ONLY_ACTIVE_ARCH = YES; 284 | SDKROOT = iphoneos; 285 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 286 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 287 | VERSIONING_SYSTEM = "apple-generic"; 288 | VERSION_INFO_PREFIX = ""; 289 | }; 290 | name = Debug; 291 | }; 292 | 13EDE57823278D8700B45FF6 /* Release */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ALWAYS_SEARCH_USER_PATHS = NO; 296 | CLANG_ANALYZER_NONNULL = YES; 297 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 298 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 299 | CLANG_CXX_LIBRARY = "libc++"; 300 | CLANG_ENABLE_MODULES = YES; 301 | CLANG_ENABLE_OBJC_ARC = YES; 302 | CLANG_ENABLE_OBJC_WEAK = YES; 303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_COMMA = YES; 306 | CLANG_WARN_CONSTANT_CONVERSION = YES; 307 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 309 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 321 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 322 | CLANG_WARN_UNREACHABLE_CODE = YES; 323 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 324 | CODE_SIGN_IDENTITY = "iPhone Developer"; 325 | COPY_PHASE_STRIP = NO; 326 | CURRENT_PROJECT_VERSION = 1; 327 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 328 | ENABLE_NS_ASSERTIONS = NO; 329 | ENABLE_STRICT_OBJC_MSGSEND = YES; 330 | GCC_C_LANGUAGE_STANDARD = gnu11; 331 | GCC_NO_COMMON_BLOCKS = YES; 332 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 333 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 334 | GCC_WARN_UNDECLARED_SELECTOR = YES; 335 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 336 | GCC_WARN_UNUSED_FUNCTION = YES; 337 | GCC_WARN_UNUSED_VARIABLE = YES; 338 | IPHONEOS_DEPLOYMENT_TARGET = 12.4; 339 | MTL_ENABLE_DEBUG_INFO = NO; 340 | MTL_FAST_MATH = YES; 341 | SDKROOT = iphoneos; 342 | SWIFT_COMPILATION_MODE = wholemodule; 343 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 344 | VALIDATE_PRODUCT = YES; 345 | VERSIONING_SYSTEM = "apple-generic"; 346 | VERSION_INFO_PREFIX = ""; 347 | }; 348 | name = Release; 349 | }; 350 | 13EDE57A23278D8700B45FF6 /* Debug */ = { 351 | isa = XCBuildConfiguration; 352 | buildSettings = { 353 | CLANG_ENABLE_MODULES = YES; 354 | CODE_SIGN_IDENTITY = ""; 355 | CODE_SIGN_STYLE = Automatic; 356 | DEFINES_MODULE = YES; 357 | DEVELOPMENT_TEAM = 7X5CB8QV9R; 358 | DYLIB_COMPATIBILITY_VERSION = 1; 359 | DYLIB_CURRENT_VERSION = 1; 360 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 361 | INFOPLIST_FILE = OTPFieldView/Info.plist; 362 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 363 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 364 | LD_RUNPATH_SEARCH_PATHS = ( 365 | "$(inherited)", 366 | "@executable_path/Frameworks", 367 | "@loader_path/Frameworks", 368 | ); 369 | PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldView; 370 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 371 | SKIP_INSTALL = YES; 372 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 373 | SWIFT_VERSION = 5.0; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | }; 376 | name = Debug; 377 | }; 378 | 13EDE57B23278D8700B45FF6 /* Release */ = { 379 | isa = XCBuildConfiguration; 380 | buildSettings = { 381 | CLANG_ENABLE_MODULES = YES; 382 | CODE_SIGN_IDENTITY = ""; 383 | CODE_SIGN_STYLE = Automatic; 384 | DEFINES_MODULE = YES; 385 | DEVELOPMENT_TEAM = 7X5CB8QV9R; 386 | DYLIB_COMPATIBILITY_VERSION = 1; 387 | DYLIB_CURRENT_VERSION = 1; 388 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 389 | INFOPLIST_FILE = OTPFieldView/Info.plist; 390 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 391 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 392 | LD_RUNPATH_SEARCH_PATHS = ( 393 | "$(inherited)", 394 | "@executable_path/Frameworks", 395 | "@loader_path/Frameworks", 396 | ); 397 | PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldView; 398 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 399 | SKIP_INSTALL = YES; 400 | SWIFT_VERSION = 5.0; 401 | TARGETED_DEVICE_FAMILY = "1,2"; 402 | }; 403 | name = Release; 404 | }; 405 | 13EDE58D2327A8C400B45FF6 /* Debug */ = { 406 | isa = XCBuildConfiguration; 407 | buildSettings = { 408 | CODE_SIGN_STYLE = Automatic; 409 | DEVELOPMENT_TEAM = 7X5CB8QV9R; 410 | INFOPLIST_FILE = OTPFieldViewTests/Info.plist; 411 | LD_RUNPATH_SEARCH_PATHS = ( 412 | "$(inherited)", 413 | "@executable_path/Frameworks", 414 | "@loader_path/Frameworks", 415 | ); 416 | PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldViewTests; 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | SWIFT_VERSION = 5.0; 419 | TARGETED_DEVICE_FAMILY = "1,2"; 420 | }; 421 | name = Debug; 422 | }; 423 | 13EDE58E2327A8C400B45FF6 /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | CODE_SIGN_STYLE = Automatic; 427 | DEVELOPMENT_TEAM = 7X5CB8QV9R; 428 | INFOPLIST_FILE = OTPFieldViewTests/Info.plist; 429 | LD_RUNPATH_SEARCH_PATHS = ( 430 | "$(inherited)", 431 | "@executable_path/Frameworks", 432 | "@loader_path/Frameworks", 433 | ); 434 | PRODUCT_BUNDLE_IDENTIFIER = com.Vaibhav.OTPFieldViewTests; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | SWIFT_VERSION = 5.0; 437 | TARGETED_DEVICE_FAMILY = "1,2"; 438 | }; 439 | name = Release; 440 | }; 441 | /* End XCBuildConfiguration section */ 442 | 443 | /* Begin XCConfigurationList section */ 444 | 13EDE56B23278D8700B45FF6 /* Build configuration list for PBXProject "OTPFieldView" */ = { 445 | isa = XCConfigurationList; 446 | buildConfigurations = ( 447 | 13EDE57723278D8700B45FF6 /* Debug */, 448 | 13EDE57823278D8700B45FF6 /* Release */, 449 | ); 450 | defaultConfigurationIsVisible = 0; 451 | defaultConfigurationName = Release; 452 | }; 453 | 13EDE57923278D8700B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldView" */ = { 454 | isa = XCConfigurationList; 455 | buildConfigurations = ( 456 | 13EDE57A23278D8700B45FF6 /* Debug */, 457 | 13EDE57B23278D8700B45FF6 /* Release */, 458 | ); 459 | defaultConfigurationIsVisible = 0; 460 | defaultConfigurationName = Release; 461 | }; 462 | 13EDE58C2327A8C400B45FF6 /* Build configuration list for PBXNativeTarget "OTPFieldViewTests" */ = { 463 | isa = XCConfigurationList; 464 | buildConfigurations = ( 465 | 13EDE58D2327A8C400B45FF6 /* Debug */, 466 | 13EDE58E2327A8C400B45FF6 /* Release */, 467 | ); 468 | defaultConfigurationIsVisible = 0; 469 | defaultConfigurationName = Release; 470 | }; 471 | /* End XCConfigurationList section */ 472 | }; 473 | rootObject = 13EDE56823278D8700B45FF6 /* Project object */; 474 | } 475 | -------------------------------------------------------------------------------- /OTPFieldView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OTPFieldView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /OTPFieldView.xcodeproj/project.xcworkspace/xcuserdata/vaibhavbhasin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/OTPFieldView.xcodeproj/project.xcworkspace/xcuserdata/vaibhavbhasin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /OTPFieldView.xcodeproj/xcuserdata/vaibhavbhasin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | OTPFieldView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | OTPFieldViewTests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /OTPFieldView/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/OTPFieldView/.DS_Store -------------------------------------------------------------------------------- /OTPFieldView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /OTPFieldView/OTPFieldView.h: -------------------------------------------------------------------------------- 1 | // 2 | // OTPFieldView.h 3 | // OTPFieldView 4 | // 5 | // Created by Vaibhav Bhasin on 10/09/19. 6 | // Copyright © 2019 Vaibhav Bhasin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for OTPFieldView. 12 | FOUNDATION_EXPORT double OTPFieldViewVersionNumber; 13 | 14 | //! Project version string for OTPFieldView. 15 | FOUNDATION_EXPORT const unsigned char OTPFieldViewVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /OTPFieldView/OTPFieldView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPFieldView.swift 3 | // OTPFieldView 4 | // 5 | // Created by Vaibhav Bhasin on 10/09/19. 6 | // Copyright © 2019 Vaibhav Bhasin. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2019 Vaibhav Bhasin 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import UIKit 32 | 33 | @objc public protocol OTPFieldViewDelegate: class { 34 | 35 | func shouldBecomeFirstResponderForOTP(otpTextFieldIndex index: Int) -> Bool 36 | func enteredOTP(otp: String) 37 | func hasEnteredAllOTP(hasEnteredAll: Bool) -> Bool 38 | } 39 | 40 | @objc public enum DisplayType: Int { 41 | case circular 42 | case roundedCorner 43 | case square 44 | case diamond 45 | case underlinedBottom 46 | } 47 | 48 | /// Different input type for OTP fields. 49 | @objc public enum KeyboardType: Int { 50 | case numeric 51 | case alphabet 52 | case alphaNumeric 53 | } 54 | 55 | @objc public class OTPFieldView: UIView { 56 | 57 | /// Different display type for text fields. 58 | 59 | 60 | public var displayType: DisplayType = .circular 61 | public var fieldsCount: Int = 4 62 | public var otpInputType: KeyboardType = .numeric 63 | public var fieldFont: UIFont = UIFont.systemFont(ofSize: 20) 64 | public var secureEntry: Bool = false 65 | public var hideEnteredText: Bool = false 66 | public var requireCursor: Bool = true 67 | public var cursorColor: UIColor = UIColor.blue 68 | public var fieldSize: CGFloat = 60 69 | public var separatorSpace: CGFloat = 16 70 | public var fieldBorderWidth: CGFloat = 1 71 | public var shouldAllowIntermediateEditing: Bool = true 72 | public var defaultBackgroundColor: UIColor = UIColor.clear 73 | public var filledBackgroundColor: UIColor = UIColor.clear 74 | public var defaultBorderColor: UIColor = UIColor.gray 75 | public var filledBorderColor: UIColor = UIColor.clear 76 | public var errorBorderColor: UIColor? 77 | 78 | public weak var delegate: OTPFieldViewDelegate? 79 | 80 | fileprivate var secureEntryData = [String]() 81 | 82 | override public func awakeFromNib() { 83 | super.awakeFromNib() 84 | } 85 | 86 | public func initializeUI() { 87 | layer.masksToBounds = true 88 | layoutIfNeeded() 89 | 90 | initializeOTPFields() 91 | 92 | layoutIfNeeded() 93 | 94 | // Forcefully try to make first otp field as first responder 95 | (viewWithTag(1) as? OTPTextField)?.becomeFirstResponder() 96 | } 97 | 98 | fileprivate func initializeOTPFields() { 99 | secureEntryData.removeAll() 100 | 101 | for index in stride(from: 0, to: fieldsCount, by: 1) { 102 | let oldOtpField = viewWithTag(index + 1) as? OTPTextField 103 | oldOtpField?.removeFromSuperview() 104 | 105 | let otpField = getOTPField(forIndex: index) 106 | addSubview(otpField) 107 | 108 | secureEntryData.append("") 109 | } 110 | } 111 | 112 | fileprivate func getOTPField(forIndex index: Int) -> OTPTextField { 113 | let hasOddNumberOfFields = (fieldsCount % 2 == 1) 114 | var fieldFrame = CGRect(x: 0, y: 0, width: fieldSize, height: fieldSize) 115 | 116 | if hasOddNumberOfFields { 117 | // Calculate from middle each fields x and y values so as to align the entire view in center 118 | fieldFrame.origin.x = bounds.size.width / 2 - (CGFloat(fieldsCount / 2 - index) * (fieldSize + separatorSpace) + fieldSize / 2) 119 | } 120 | else { 121 | // Calculate from middle each fields x and y values so as to align the entire view in center 122 | fieldFrame.origin.x = bounds.size.width / 2 - (CGFloat(fieldsCount / 2 - index) * fieldSize + CGFloat(fieldsCount / 2 - index - 1) * separatorSpace + separatorSpace / 2) 123 | } 124 | 125 | fieldFrame.origin.y = (bounds.size.height - fieldSize) / 2 126 | 127 | let otpField = OTPTextField(frame: fieldFrame) 128 | otpField.delegate = self 129 | otpField.tag = index + 1 130 | otpField.font = fieldFont 131 | 132 | // Set input type for OTP fields 133 | switch otpInputType { 134 | case .numeric: 135 | otpField.keyboardType = .numberPad 136 | case .alphabet: 137 | otpField.keyboardType = .alphabet 138 | case .alphaNumeric: 139 | otpField.keyboardType = .namePhonePad 140 | } 141 | 142 | // Set the border values if needed 143 | otpField.otpBorderColor = defaultBorderColor 144 | otpField.otpBorderWidth = fieldBorderWidth 145 | 146 | if requireCursor { 147 | otpField.tintColor = cursorColor 148 | } 149 | else { 150 | otpField.tintColor = UIColor.clear 151 | } 152 | 153 | // Set the default background color when text not set 154 | otpField.backgroundColor = defaultBackgroundColor 155 | 156 | // Finally create the fields 157 | otpField.initalizeUI(forFieldType: displayType) 158 | 159 | return otpField 160 | } 161 | 162 | fileprivate func isPreviousFieldsEntered(forTextField textField: UITextField) -> Bool { 163 | var isTextFilled = true 164 | var nextOTPField: UITextField? 165 | 166 | // If intermediate editing is not allowed, then check for last filled field in forward direction. 167 | if !shouldAllowIntermediateEditing { 168 | for index in stride(from: 1, to: fieldsCount + 1, by: 1) { 169 | let tempNextOTPField = viewWithTag(index) as? UITextField 170 | 171 | if let tempNextOTPFieldText = tempNextOTPField?.text, tempNextOTPFieldText.isEmpty { 172 | nextOTPField = tempNextOTPField 173 | 174 | break 175 | } 176 | } 177 | 178 | if let nextOTPField = nextOTPField { 179 | isTextFilled = (nextOTPField == textField || (textField.tag) == (nextOTPField.tag - 1)) 180 | } 181 | } 182 | 183 | return isTextFilled 184 | } 185 | 186 | // Helper function to get the OTP String entered 187 | fileprivate func calculateEnteredOTPSTring(isDeleted: Bool) { 188 | if isDeleted { 189 | _ = delegate?.hasEnteredAllOTP(hasEnteredAll: false) 190 | 191 | // Set the default enteres state for otp entry 192 | for index in stride(from: 0, to: fieldsCount, by: 1) { 193 | var otpField = viewWithTag(index + 1) as? OTPTextField 194 | 195 | if otpField == nil { 196 | otpField = getOTPField(forIndex: index) 197 | } 198 | 199 | let fieldBackgroundColor = (otpField?.text ?? "").isEmpty ? defaultBackgroundColor : filledBackgroundColor 200 | let fieldBorderColor = (otpField?.text ?? "").isEmpty ? defaultBorderColor : filledBorderColor 201 | 202 | if displayType == .diamond || displayType == .underlinedBottom { 203 | otpField?.shapeLayer.fillColor = fieldBackgroundColor.cgColor 204 | otpField?.shapeLayer.strokeColor = fieldBorderColor.cgColor 205 | } else { 206 | otpField?.backgroundColor = fieldBackgroundColor 207 | otpField?.layer.borderColor = fieldBorderColor.cgColor 208 | } 209 | } 210 | } 211 | else { 212 | var enteredOTPString = "" 213 | 214 | // Check for entered OTP 215 | for index in stride(from: 0, to: secureEntryData.count, by: 1) { 216 | if !secureEntryData[index].isEmpty { 217 | enteredOTPString.append(secureEntryData[index]) 218 | } 219 | } 220 | 221 | if enteredOTPString.count == fieldsCount { 222 | delegate?.enteredOTP(otp: enteredOTPString) 223 | 224 | // Check if all OTP fields have been filled or not. Based on that call the 2 delegate methods. 225 | let isValid = delegate?.hasEnteredAllOTP(hasEnteredAll: (enteredOTPString.count == fieldsCount)) ?? false 226 | 227 | // Set the error state for invalid otp entry 228 | for index in stride(from: 0, to: fieldsCount, by: 1) { 229 | var otpField = viewWithTag(index + 1) as? OTPTextField 230 | 231 | if otpField == nil { 232 | otpField = getOTPField(forIndex: index) 233 | } 234 | 235 | if !isValid { 236 | // Set error border color if set, if not, set default border color 237 | otpField?.layer.borderColor = (errorBorderColor ?? filledBorderColor).cgColor 238 | } 239 | else { 240 | otpField?.layer.borderColor = filledBorderColor.cgColor 241 | } 242 | } 243 | } 244 | } 245 | } 246 | 247 | } 248 | 249 | extension OTPFieldView: UITextFieldDelegate { 250 | public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { 251 | let shouldBeginEditing = delegate?.shouldBecomeFirstResponderForOTP(otpTextFieldIndex: (textField.tag - 1)) ?? true 252 | if shouldBeginEditing { 253 | return isPreviousFieldsEntered(forTextField: textField) 254 | } 255 | 256 | return shouldBeginEditing 257 | } 258 | 259 | public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 260 | let replacedText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) ?? "" 261 | 262 | // Check since only alphabet keyboard is not available in iOS 263 | if !replacedText.isEmpty && otpInputType == .alphabet && replacedText.rangeOfCharacter(from: .letters) == nil { 264 | return false 265 | } 266 | 267 | if replacedText.count >= 1 { 268 | // If field has a text already, then replace the text and move to next field if present 269 | secureEntryData[textField.tag - 1] = string 270 | 271 | if hideEnteredText { 272 | textField.text = " " 273 | } 274 | else { 275 | if secureEntry { 276 | textField.text = "•" 277 | } 278 | else { 279 | textField.text = string 280 | } 281 | } 282 | 283 | if displayType == .diamond || displayType == .underlinedBottom { 284 | (textField as! OTPTextField).shapeLayer.fillColor = filledBackgroundColor.cgColor 285 | (textField as! OTPTextField).shapeLayer.strokeColor = filledBorderColor.cgColor 286 | } 287 | else { 288 | textField.backgroundColor = filledBackgroundColor 289 | textField.layer.borderColor = filledBorderColor.cgColor 290 | } 291 | 292 | let nextOTPField = viewWithTag(textField.tag + 1) 293 | 294 | if let nextOTPField = nextOTPField { 295 | nextOTPField.becomeFirstResponder() 296 | } 297 | else { 298 | textField.resignFirstResponder() 299 | } 300 | 301 | // Get the entered string 302 | calculateEnteredOTPSTring(isDeleted: false) 303 | } 304 | else { 305 | let currentText = textField.text ?? "" 306 | 307 | if textField.tag > 1 && currentText.isEmpty { 308 | if let prevOTPField = viewWithTag(textField.tag - 1) as? UITextField { 309 | deleteText(in: prevOTPField) 310 | } 311 | } else { 312 | deleteText(in: textField) 313 | 314 | if textField.tag > 1 { 315 | if let prevOTPField = viewWithTag(textField.tag - 1) as? UITextField { 316 | prevOTPField.becomeFirstResponder() 317 | } 318 | } 319 | } 320 | } 321 | 322 | return false 323 | } 324 | 325 | private func deleteText(in textField: UITextField) { 326 | // If deleting the text, then move to previous text field if present 327 | secureEntryData[textField.tag - 1] = "" 328 | textField.text = "" 329 | 330 | if displayType == .diamond || displayType == .underlinedBottom { 331 | (textField as! OTPTextField).shapeLayer.fillColor = defaultBackgroundColor.cgColor 332 | (textField as! OTPTextField).shapeLayer.strokeColor = defaultBorderColor.cgColor 333 | } else { 334 | textField.backgroundColor = defaultBackgroundColor 335 | textField.layer.borderColor = defaultBorderColor.cgColor 336 | } 337 | 338 | textField.becomeFirstResponder() 339 | 340 | // Get the entered string 341 | calculateEnteredOTPSTring(isDeleted: true) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /OTPFieldView/OTPTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPTextField.swift 3 | // OTPFieldView 4 | // 5 | // Created by Vaibhav Bhasin on 10/09/19. 6 | // Copyright © 2019 Vaibhav Bhasin. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2019 Vaibhav Bhasin 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import UIKit 32 | 33 | 34 | @objc class OTPTextField: UITextField { 35 | /// Border color info for field 36 | public var otpBorderColor: UIColor = UIColor.black 37 | 38 | /// Border width info for field 39 | public var otpBorderWidth: CGFloat = 2 40 | 41 | public var shapeLayer: CAShapeLayer! 42 | 43 | override func awakeFromNib() { 44 | super.awakeFromNib() 45 | } 46 | 47 | override init(frame: CGRect) { 48 | super.init(frame: frame) 49 | } 50 | 51 | required init?(coder aDecoder: NSCoder) { 52 | super.init(coder: aDecoder) 53 | } 54 | 55 | public func initalizeUI(forFieldType type: DisplayType) { 56 | switch type { 57 | case .circular: 58 | layer.cornerRadius = bounds.size.width / 2 59 | break 60 | case .roundedCorner: 61 | layer.cornerRadius = 4 62 | break 63 | case .square: 64 | layer.cornerRadius = 0 65 | break 66 | case .diamond: 67 | addDiamondMask() 68 | break 69 | case .underlinedBottom: 70 | addBottomView() 71 | break 72 | } 73 | 74 | // Basic UI setup 75 | if type != .diamond && type != .underlinedBottom { 76 | layer.borderColor = otpBorderColor.cgColor 77 | layer.borderWidth = otpBorderWidth 78 | } 79 | 80 | autocorrectionType = .no 81 | textAlignment = .center 82 | if #available(iOS 12.0, *) { 83 | textContentType = .oneTimeCode 84 | } 85 | } 86 | 87 | override func deleteBackward() { 88 | super.deleteBackward() 89 | 90 | _ = delegate?.textField?(self, shouldChangeCharactersIn: NSMakeRange(0, 0), replacementString: "") 91 | } 92 | 93 | // Helper function to create diamond view 94 | fileprivate func addDiamondMask() { 95 | let path = UIBezierPath() 96 | path.move(to: CGPoint(x: bounds.size.width / 2.0, y: 0)) 97 | path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height / 2.0)) 98 | path.addLine(to: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height)) 99 | path.addLine(to: CGPoint(x: 0, y: bounds.size.height / 2.0)) 100 | path.close() 101 | 102 | let maskLayer = CAShapeLayer() 103 | maskLayer.path = path.cgPath 104 | 105 | layer.mask = maskLayer 106 | 107 | shapeLayer = CAShapeLayer() 108 | shapeLayer.path = path.cgPath 109 | shapeLayer.lineWidth = otpBorderWidth 110 | shapeLayer.fillColor = backgroundColor?.cgColor 111 | shapeLayer.strokeColor = otpBorderColor.cgColor 112 | 113 | layer.addSublayer(shapeLayer) 114 | } 115 | 116 | // Helper function to create a underlined bottom view 117 | fileprivate func addBottomView() { 118 | let path = UIBezierPath() 119 | path.move(to: CGPoint(x: 0, y: bounds.size.height)) 120 | path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height)) 121 | path.close() 122 | 123 | shapeLayer = CAShapeLayer() 124 | shapeLayer.path = path.cgPath 125 | shapeLayer.lineWidth = otpBorderWidth 126 | shapeLayer.fillColor = backgroundColor?.cgColor 127 | shapeLayer.strokeColor = otpBorderColor.cgColor 128 | 129 | layer.addSublayer(shapeLayer) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /OTPFieldViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /OTPFieldViewTests/OTPFieldViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OTPFieldViewTests.swift 3 | // OTPFieldViewTests 4 | // 5 | // Created by Vaibhav Bhasin on 10/09/19. 6 | // Copyright © 2019 Vaibhav Bhasin. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class OTPFieldViewTests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDown() { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OTPFieldView 2 | One Time Password View for iOS. Built in Swift 5 3 | 4 |

5 | Swift 5 compatible 6 | Platform iOS 7 | License: MIT 8 |

9 | 10 | ### Preview 11 | 12 | ![demo](Screenshots/IMG_0198.PNG) 13 | ![demo](Screenshots/IMG_0199.PNG) 14 | ![demo](Screenshots/IMG_0200.PNG) 15 | ![demo](Screenshots/IMG_0201.PNG) 16 | ![demo](Screenshots/IMG_0202.PNG) 17 | 18 | ## Installation 19 | 20 | ### CocoaPods 21 | 22 | Add the following line to your Podfile: 23 | 24 | ```ruby 25 | pod 'OTPFieldView' 26 | ``` 27 | 28 | Then run the following in the same directory as your Podfile: 29 | ```ruby 30 | pod install 31 | ``` 32 | 33 | ## Usage 34 | 35 | ### Code 36 | ```swift 37 | @IBOutlet var otpTextFieldView: OTPFieldView! 38 | 39 | func setupOtpView(){ 40 | self.otpTextFieldView.fieldsCount = 5 41 | self.otpTextFieldView.fieldBorderWidth = 2 42 | self.otpTextFieldView.defaultBorderColor = UIColor.black 43 | self.otpTextFieldView.filledBorderColor = UIColor.green 44 | self.otpTextFieldView.cursorColor = UIColor.red 45 | self.otpTextFieldView.displayType = .underlinedBottom 46 | self.otpTextFieldView.fieldSize = 40 47 | self.otpTextFieldView.separatorSpace = 8 48 | self.otpTextFieldView.shouldAllowIntermediateEditing = false 49 | self.otpTextFieldView.delegate = self 50 | self.otpTextFieldView.initializeUI() 51 | } 52 | ``` 53 | The `becomeFirstResponderAtIndex` property sets the pinField at the specified index as the first responder. 54 | The `isContentTypeOneTimeCode` property sets the contentType of the first pinField to `.oneTimeCode` to leverage the iOS 12 feature where the passcode is directly fetched from the messages. 55 | 56 | #### Styles 57 | ```swift 58 | enum DisplayType: Int { 59 | case circular 60 | case roundedCorner 61 | case square 62 | case diamond 63 | case underlinedBottom 64 | } 65 | ``` 66 | 67 | ### Delegate Methods 68 | 69 | ```swift 70 | extension OtpViewController: OTPFieldViewDelegate { 71 | func hasEnteredAllOTP(hasEnteredAll hasEntered: Bool) -> Bool { 72 | print("Has entered all OTP? \(hasEntered)") 73 | return false 74 | } 75 | 76 | func shouldBecomeFirstResponderForOTP(otpTextFieldIndex index: Int) -> Bool { 77 | return true 78 | } 79 | 80 | func enteredOTP(otp otpString: String) { 81 | print("OTPString: \(otpString)") 82 | } 83 | } 84 | ``` 85 | 86 | - **hasEnteredAllOTP()**: Returns true when all text Fields are full. 87 | - **shouldBecomeFirstResponderForOTP()**: Show keyboard automatically. 88 | - **enteredOTP()**: Get entered pin. 89 | 90 | ### Properties 91 | 92 | - **.displayType**: Display type for Text Field. 93 | - **.fieldsCount**: Length of OTP. 94 | - **.otpInputType**: Input type for Text Field : numeric, alphabet, alphaNumeric. 95 | 96 | - **.fieldFont**: Font for Text Field. 97 | - **.secureEntry**: Shows • instead of text. 98 | - **.hideEnteredText**: Hides the text. 99 | - **.requireCursor**: Shows/Hides cursor. 100 | - **.cursorColor**: Color for Cursor. 101 | - **.fieldSize**: Size of Text Field. 102 | - **.separatorSpace**: Space between Text Fields. 103 | - **.fieldBorderWidth**: Border width for Text Fields. 104 | - **.shouldAllowIntermediateEditing**: Allow to edit from middle. 105 | - **.defaultBackgroundColor**: Empty text field background color. 106 | - **.filledBackgroundColor**: Filled text field background color. 107 | - **.defaultBorderColor**: Empty text field border color. 108 | - **.filledBorderColor**: Filled text field border color. 109 | - **.errorBorderColor**: Error text field border color. 110 | - **.delegate**: delegate. 111 | 112 | ## License 113 | 114 | OTPFieldView is available under the MIT license. See LICENSE for details. 115 | -------------------------------------------------------------------------------- /Screenshots/IMG_0198.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/Screenshots/IMG_0198.PNG -------------------------------------------------------------------------------- /Screenshots/IMG_0199.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/Screenshots/IMG_0199.PNG -------------------------------------------------------------------------------- /Screenshots/IMG_0200.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/Screenshots/IMG_0200.PNG -------------------------------------------------------------------------------- /Screenshots/IMG_0201.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/Screenshots/IMG_0201.PNG -------------------------------------------------------------------------------- /Screenshots/IMG_0202.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Root-vb/OTPFieldView/ed198372acc9bdd2c3a611af6cfb654be044384b/Screenshots/IMG_0202.PNG --------------------------------------------------------------------------------