├── .gitignore ├── PHOTPView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── pankti.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── pankti28.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── pankti.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── pankti28.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── PHOTPView ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Classes │ └── PinViewController.swift ├── Info.plist └── PHOPTView │ ├── .DS_Store │ ├── PHOTPConfig.swift │ ├── PHOTPTextField.swift │ └── PHOTPView.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't track content of these folders 2 | node_modules/ 3 | someOtherfoler/ 4 | 5 | 6 | # Compiled source # 7 | ################### 8 | *.com 9 | *.class 10 | *.dll 11 | *.exe 12 | *.o 13 | *.so 14 | 15 | *.so 16 | 17 | # Packages # 18 | ############ 19 | # it's better to unpack these files and commit the raw source 20 | # git has its own built in compression methods 21 | *.7z 22 | *.dmg 23 | *.gz 24 | *.iso 25 | *.jar 26 | *.rar 27 | *.tar 28 | *.zip 29 | -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F0DD1F3923032F780013329C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DD1F3823032F780013329C /* AppDelegate.swift */; }; 11 | F0DD1F4023032F790013329C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F0DD1F3F23032F790013329C /* Assets.xcassets */; }; 12 | F0DD1F4323032F790013329C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F0DD1F4123032F790013329C /* LaunchScreen.storyboard */; }; 13 | F0DD1F6823032FB80013329C /* PinViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DD1F6723032FB80013329C /* PinViewController.swift */; }; 14 | F0DD1F6C230330370013329C /* PHOTPView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DD1F6A230330370013329C /* PHOTPView.swift */; }; 15 | F0DD1F6D230330370013329C /* PHOTPTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DD1F6B230330370013329C /* PHOTPTextField.swift */; }; 16 | F0DD1F6F230332430013329C /* PHOTPConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DD1F6E230332430013329C /* PHOTPConfig.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | F0DD1F3523032F780013329C /* PHOTPView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PHOTPView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | F0DD1F3823032F780013329C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | F0DD1F3F23032F790013329C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | F0DD1F4223032F790013329C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | F0DD1F4423032F790013329C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | F0DD1F6723032FB80013329C /* PinViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinViewController.swift; sourceTree = ""; }; 26 | F0DD1F6A230330370013329C /* PHOTPView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PHOTPView.swift; sourceTree = ""; }; 27 | F0DD1F6B230330370013329C /* PHOTPTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PHOTPTextField.swift; sourceTree = ""; }; 28 | F0DD1F6E230332430013329C /* PHOTPConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHOTPConfig.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | F0DD1F3223032F780013329C /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | F0DD1F2C23032F780013329C = { 43 | isa = PBXGroup; 44 | children = ( 45 | F0DD1F3723032F780013329C /* PHOTPView */, 46 | F0DD1F3623032F780013329C /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | F0DD1F3623032F780013329C /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | F0DD1F3523032F780013329C /* PHOTPView.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | F0DD1F3723032F780013329C /* PHOTPView */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | F0DD1F69230330370013329C /* PHOPTView */, 62 | F0DD1F6623032F9A0013329C /* Classes */, 63 | F0DD1F3823032F780013329C /* AppDelegate.swift */, 64 | F0DD1F3F23032F790013329C /* Assets.xcassets */, 65 | F0DD1F4123032F790013329C /* LaunchScreen.storyboard */, 66 | F0DD1F4423032F790013329C /* Info.plist */, 67 | ); 68 | path = PHOTPView; 69 | sourceTree = ""; 70 | }; 71 | F0DD1F6623032F9A0013329C /* Classes */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | F0DD1F6723032FB80013329C /* PinViewController.swift */, 75 | ); 76 | path = Classes; 77 | sourceTree = ""; 78 | }; 79 | F0DD1F69230330370013329C /* PHOPTView */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | F0DD1F6A230330370013329C /* PHOTPView.swift */, 83 | F0DD1F6B230330370013329C /* PHOTPTextField.swift */, 84 | F0DD1F6E230332430013329C /* PHOTPConfig.swift */, 85 | ); 86 | path = PHOPTView; 87 | sourceTree = ""; 88 | }; 89 | /* End PBXGroup section */ 90 | 91 | /* Begin PBXNativeTarget section */ 92 | F0DD1F3423032F780013329C /* PHOTPView */ = { 93 | isa = PBXNativeTarget; 94 | buildConfigurationList = F0DD1F5D23032F790013329C /* Build configuration list for PBXNativeTarget "PHOTPView" */; 95 | buildPhases = ( 96 | F0DD1F3123032F780013329C /* Sources */, 97 | F0DD1F3223032F780013329C /* Frameworks */, 98 | F0DD1F3323032F780013329C /* Resources */, 99 | ); 100 | buildRules = ( 101 | ); 102 | dependencies = ( 103 | ); 104 | name = PHOTPView; 105 | productName = PPOTPView; 106 | productReference = F0DD1F3523032F780013329C /* PHOTPView.app */; 107 | productType = "com.apple.product-type.application"; 108 | }; 109 | /* End PBXNativeTarget section */ 110 | 111 | /* Begin PBXProject section */ 112 | F0DD1F2D23032F780013329C /* Project object */ = { 113 | isa = PBXProject; 114 | attributes = { 115 | LastSwiftUpdateCheck = 1030; 116 | LastUpgradeCheck = 1030; 117 | ORGANIZATIONNAME = "Pankti Patel"; 118 | TargetAttributes = { 119 | F0DD1F3423032F780013329C = { 120 | CreatedOnToolsVersion = 10.3; 121 | }; 122 | }; 123 | }; 124 | buildConfigurationList = F0DD1F3023032F780013329C /* Build configuration list for PBXProject "PHOTPView" */; 125 | compatibilityVersion = "Xcode 9.3"; 126 | developmentRegion = en; 127 | hasScannedForEncodings = 0; 128 | knownRegions = ( 129 | en, 130 | Base, 131 | ); 132 | mainGroup = F0DD1F2C23032F780013329C; 133 | productRefGroup = F0DD1F3623032F780013329C /* Products */; 134 | projectDirPath = ""; 135 | projectRoot = ""; 136 | targets = ( 137 | F0DD1F3423032F780013329C /* PHOTPView */, 138 | ); 139 | }; 140 | /* End PBXProject section */ 141 | 142 | /* Begin PBXResourcesBuildPhase section */ 143 | F0DD1F3323032F780013329C /* Resources */ = { 144 | isa = PBXResourcesBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | F0DD1F4323032F790013329C /* LaunchScreen.storyboard in Resources */, 148 | F0DD1F4023032F790013329C /* Assets.xcassets in Resources */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | /* End PBXResourcesBuildPhase section */ 153 | 154 | /* Begin PBXSourcesBuildPhase section */ 155 | F0DD1F3123032F780013329C /* Sources */ = { 156 | isa = PBXSourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | F0DD1F6C230330370013329C /* PHOTPView.swift in Sources */, 160 | F0DD1F6D230330370013329C /* PHOTPTextField.swift in Sources */, 161 | F0DD1F6F230332430013329C /* PHOTPConfig.swift in Sources */, 162 | F0DD1F3923032F780013329C /* AppDelegate.swift in Sources */, 163 | F0DD1F6823032FB80013329C /* PinViewController.swift in Sources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXSourcesBuildPhase section */ 168 | 169 | /* Begin PBXVariantGroup section */ 170 | F0DD1F4123032F790013329C /* LaunchScreen.storyboard */ = { 171 | isa = PBXVariantGroup; 172 | children = ( 173 | F0DD1F4223032F790013329C /* Base */, 174 | ); 175 | name = LaunchScreen.storyboard; 176 | sourceTree = ""; 177 | }; 178 | /* End PBXVariantGroup section */ 179 | 180 | /* Begin XCBuildConfiguration section */ 181 | F0DD1F5B23032F790013329C /* Debug */ = { 182 | isa = XCBuildConfiguration; 183 | buildSettings = { 184 | ALWAYS_SEARCH_USER_PATHS = NO; 185 | CLANG_ANALYZER_NONNULL = YES; 186 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 188 | CLANG_CXX_LIBRARY = "libc++"; 189 | CLANG_ENABLE_MODULES = YES; 190 | CLANG_ENABLE_OBJC_ARC = YES; 191 | CLANG_ENABLE_OBJC_WEAK = YES; 192 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 193 | CLANG_WARN_BOOL_CONVERSION = YES; 194 | CLANG_WARN_COMMA = YES; 195 | CLANG_WARN_CONSTANT_CONVERSION = YES; 196 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 197 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 198 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 199 | CLANG_WARN_EMPTY_BODY = YES; 200 | CLANG_WARN_ENUM_CONVERSION = YES; 201 | CLANG_WARN_INFINITE_RECURSION = YES; 202 | CLANG_WARN_INT_CONVERSION = YES; 203 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 204 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 205 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 207 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 208 | CLANG_WARN_STRICT_PROTOTYPES = YES; 209 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 210 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 211 | CLANG_WARN_UNREACHABLE_CODE = YES; 212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 213 | CODE_SIGN_IDENTITY = "iPhone Developer"; 214 | COPY_PHASE_STRIP = NO; 215 | DEBUG_INFORMATION_FORMAT = dwarf; 216 | ENABLE_STRICT_OBJC_MSGSEND = YES; 217 | ENABLE_TESTABILITY = YES; 218 | GCC_C_LANGUAGE_STANDARD = gnu11; 219 | GCC_DYNAMIC_NO_PIC = NO; 220 | GCC_NO_COMMON_BLOCKS = YES; 221 | GCC_OPTIMIZATION_LEVEL = 0; 222 | GCC_PREPROCESSOR_DEFINITIONS = ( 223 | "DEBUG=1", 224 | "$(inherited)", 225 | ); 226 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 227 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 228 | GCC_WARN_UNDECLARED_SELECTOR = YES; 229 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 230 | GCC_WARN_UNUSED_FUNCTION = YES; 231 | GCC_WARN_UNUSED_VARIABLE = YES; 232 | IPHONEOS_DEPLOYMENT_TARGET = 12.4; 233 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 234 | MTL_FAST_MATH = YES; 235 | ONLY_ACTIVE_ARCH = YES; 236 | SDKROOT = iphoneos; 237 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 238 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 239 | }; 240 | name = Debug; 241 | }; 242 | F0DD1F5C23032F790013329C /* Release */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_NONNULL = YES; 247 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_ENABLE_OBJC_WEAK = YES; 253 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 254 | CLANG_WARN_BOOL_CONVERSION = YES; 255 | CLANG_WARN_COMMA = YES; 256 | CLANG_WARN_CONSTANT_CONVERSION = YES; 257 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 260 | CLANG_WARN_EMPTY_BODY = YES; 261 | CLANG_WARN_ENUM_CONVERSION = YES; 262 | CLANG_WARN_INFINITE_RECURSION = YES; 263 | CLANG_WARN_INT_CONVERSION = YES; 264 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 266 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 268 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 269 | CLANG_WARN_STRICT_PROTOTYPES = YES; 270 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 271 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 272 | CLANG_WARN_UNREACHABLE_CODE = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | CODE_SIGN_IDENTITY = "iPhone Developer"; 275 | COPY_PHASE_STRIP = NO; 276 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 277 | ENABLE_NS_ASSERTIONS = NO; 278 | ENABLE_STRICT_OBJC_MSGSEND = YES; 279 | GCC_C_LANGUAGE_STANDARD = gnu11; 280 | GCC_NO_COMMON_BLOCKS = YES; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 12.4; 288 | MTL_ENABLE_DEBUG_INFO = NO; 289 | MTL_FAST_MATH = YES; 290 | SDKROOT = iphoneos; 291 | SWIFT_COMPILATION_MODE = wholemodule; 292 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 293 | VALIDATE_PRODUCT = YES; 294 | }; 295 | name = Release; 296 | }; 297 | F0DD1F5E23032F790013329C /* Debug */ = { 298 | isa = XCBuildConfiguration; 299 | buildSettings = { 300 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 301 | CODE_SIGN_STYLE = Automatic; 302 | DEVELOPMENT_TEAM = 7B68JHP9GS; 303 | INFOPLIST_FILE = PHOTPView/Info.plist; 304 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 305 | LD_RUNPATH_SEARCH_PATHS = ( 306 | "$(inherited)", 307 | "@executable_path/Frameworks", 308 | ); 309 | PRODUCT_BUNDLE_IDENTIFIER = com.syntronic.PHOTPView; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | SWIFT_VERSION = 5.0; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Debug; 315 | }; 316 | F0DD1F5F23032F790013329C /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | CODE_SIGN_STYLE = Automatic; 321 | DEVELOPMENT_TEAM = 7B68JHP9GS; 322 | INFOPLIST_FILE = PHOTPView/Info.plist; 323 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 324 | LD_RUNPATH_SEARCH_PATHS = ( 325 | "$(inherited)", 326 | "@executable_path/Frameworks", 327 | ); 328 | PRODUCT_BUNDLE_IDENTIFIER = com.syntronic.PHOTPView; 329 | PRODUCT_NAME = "$(TARGET_NAME)"; 330 | SWIFT_VERSION = 5.0; 331 | TARGETED_DEVICE_FAMILY = "1,2"; 332 | }; 333 | name = Release; 334 | }; 335 | /* End XCBuildConfiguration section */ 336 | 337 | /* Begin XCConfigurationList section */ 338 | F0DD1F3023032F780013329C /* Build configuration list for PBXProject "PHOTPView" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | F0DD1F5B23032F790013329C /* Debug */, 342 | F0DD1F5C23032F790013329C /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | F0DD1F5D23032F790013329C /* Build configuration list for PBXNativeTarget "PHOTPView" */ = { 348 | isa = XCConfigurationList; 349 | buildConfigurations = ( 350 | F0DD1F5E23032F790013329C /* Debug */, 351 | F0DD1F5F23032F790013329C /* Release */, 352 | ); 353 | defaultConfigurationIsVisible = 0; 354 | defaultConfigurationName = Release; 355 | }; 356 | /* End XCConfigurationList section */ 357 | }; 358 | rootObject = F0DD1F2D23032F780013329C /* Project object */; 359 | } 360 | -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/project.xcworkspace/xcuserdata/pankti.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankti28/PHOTPView/87846f8ac0cea6dadc1e8e58f753f46599cc5679/PHOTPView.xcodeproj/project.xcworkspace/xcuserdata/pankti.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/project.xcworkspace/xcuserdata/pankti28.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankti28/PHOTPView/87846f8ac0cea6dadc1e8e58f753f46599cc5679/PHOTPView.xcodeproj/project.xcworkspace/xcuserdata/pankti28.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/xcuserdata/pankti.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PHOTPView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | PPOTPView.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/xcuserdata/pankti28.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /PHOTPView.xcodeproj/xcuserdata/pankti28.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PPOTPView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PHOTPView/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankti28/PHOTPView/87846f8ac0cea6dadc1e8e58f753f46599cc5679/PHOTPView/.DS_Store -------------------------------------------------------------------------------- /PHOTPView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PHOTPView 4 | // 5 | // Created by Pankti Patel on 2019-08-13. 6 | // Copyright © 2019 Pankti Patel. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 18 | { 19 | // Override point for customization after application launch. 20 | window = UIWindow(frame:UIScreen.main.bounds) 21 | window?.backgroundColor = UIColor.white 22 | window?.rootViewController = PinViewController() 23 | window?.makeKeyAndVisible() 24 | 25 | return true 26 | } 27 | 28 | func applicationWillResignActive(_ application: UIApplication) { 29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 30 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 31 | } 32 | 33 | func applicationDidEnterBackground(_ application: UIApplication) { 34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | func applicationWillEnterForeground(_ application: UIApplication) { 39 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | func applicationDidBecomeActive(_ application: UIApplication) { 43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 44 | } 45 | 46 | func applicationWillTerminate(_ application: UIApplication) { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /PHOTPView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /PHOTPView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PHOTPView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /PHOTPView/Classes/PinViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinViewController.swift 3 | // PHOTPView 4 | // 5 | // Created by Pankti Patel on 2019-08-13. 6 | // Copyright © 2019 Pankti Patel. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PinViewController: UIViewController 12 | { 13 | 14 | var otpView: PHOTPView! 15 | var enteredOtp: String = "" 16 | 17 | var config:PinConfig! = PinConfig() 18 | 19 | 20 | var originalCodeLabel : UILabel = 21 | { 22 | let label = UILabel() 23 | label.text = "Code to Match" 24 | label.textAlignment = NSTextAlignment.center 25 | label.translatesAutoresizingMaskIntoConstraints = false 26 | label.clipsToBounds = true 27 | return label 28 | }() 29 | 30 | var varificationCodeTextField : UITextField = 31 | { 32 | let textfield = UITextField() 33 | textfield.borderStyle = .roundedRect 34 | textfield.placeholder = "Enter code to verify" 35 | textfield.text = "253674" 36 | textfield.textAlignment = NSTextAlignment.center 37 | textfield.keyboardType = .numberPad 38 | textfield.translatesAutoresizingMaskIntoConstraints = false 39 | textfield.clipsToBounds = true 40 | return textfield 41 | }() 42 | 43 | // MARK: - LIFE CYCLE 44 | override func viewDidLoad() 45 | { 46 | super.viewDidLoad() 47 | setUpUI() 48 | } 49 | 50 | override func didReceiveMemoryWarning() 51 | { 52 | super.didReceiveMemoryWarning() 53 | // Dispose of any resources that can be recreated. 54 | } 55 | } 56 | 57 | extension PinViewController: PHOTPViewDelegate 58 | { 59 | func hasEnteredAllOTP(hasEntered: Bool) -> Bool 60 | { 61 | print("Has entered all OTP? \(hasEntered)") 62 | return enteredOtp == varificationCodeTextField.text! 63 | } 64 | 65 | func shouldBecomeFirstResponderForOTP(otpFieldIndex index: Int) -> Bool 66 | { 67 | return true 68 | } 69 | 70 | func getenteredOTP(otpString: String) { 71 | enteredOtp = otpString 72 | print("OTPString: \(otpString)") 73 | } 74 | } 75 | 76 | extension PinViewController 77 | { 78 | 79 | func setUpUI() 80 | { 81 | setupVarificationPINView() 82 | setUpVarificationCodeTextfield() 83 | setUpOriginalCodeLabel() 84 | } 85 | private func setupVarificationPINView() 86 | { 87 | config.otpFieldDisplayType = .square 88 | config.otpFieldSeparatorSpace = 10 89 | config.otpFieldSize = 40 90 | config.otpFieldsCount = 6 91 | config.otpFieldDefaultBorderColor = UIColor.blue 92 | config.otpFieldEnteredBorderColor = UIColor(red: 162/255.0, green: 179/255.0, blue: 199/255.0, alpha: 1.0)//A2B3C7 93 | config.otpFieldErrorBorderColor = UIColor.red 94 | config.otpFieldBorderWidth = 2 95 | config.shouldAllowIntermediateEditing = false 96 | 97 | otpView = PHOTPView(config: config) 98 | otpView.delegate = self 99 | 100 | self.view.addSubview(otpView) 101 | setConstraintForPinView() 102 | 103 | // Create the UI 104 | otpView.initializeUI() 105 | } 106 | 107 | private func setConstraintForPinView() 108 | { 109 | otpView.translatesAutoresizingMaskIntoConstraints = false 110 | otpView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true 111 | otpView.heightAnchor.constraint(equalToConstant: 60).isActive = true 112 | otpView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 30).isActive = true 113 | otpView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -30).isActive = true 114 | } 115 | 116 | private func setUpVarificationCodeTextfield() 117 | { 118 | self.view.addSubview(varificationCodeTextField) 119 | 120 | varificationCodeTextField.bottomAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -30).isActive = true 121 | varificationCodeTextField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true 122 | varificationCodeTextField.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 40).isActive = true 123 | varificationCodeTextField.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -40).isActive = true 124 | varificationCodeTextField.heightAnchor.constraint(equalToConstant: 40).isActive = true 125 | } 126 | private func setUpOriginalCodeLabel() 127 | { 128 | self.view.addSubview(originalCodeLabel) 129 | 130 | originalCodeLabel.bottomAnchor.constraint(equalTo: self.varificationCodeTextField.topAnchor, constant: -30).isActive = true 131 | originalCodeLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true 132 | originalCodeLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 40).isActive = true 133 | originalCodeLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -40).isActive = true 134 | originalCodeLabel.heightAnchor.constraint(equalToConstant: 30).isActive = true 135 | } 136 | 137 | } 138 | extension PinViewController 139 | { 140 | ///Actions 141 | 142 | } 143 | -------------------------------------------------------------------------------- /PHOTPView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /PHOTPView/PHOPTView/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankti28/PHOTPView/87846f8ac0cea6dadc1e8e58f753f46599cc5679/PHOTPView/PHOPTView/.DS_Store -------------------------------------------------------------------------------- /PHOTPView/PHOPTView/PHOTPConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PHOTPConfig.swift 3 | // PHOTPView 4 | // Version 1.0 5 | // 6 | // Created by Pankti Patel on 2019-08-13. 7 | // Copyright © 2019 Pankti Patel. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import UIKit 12 | 13 | 14 | /// Different display type for text fields. 15 | public enum DisplayType 16 | { 17 | case circular 18 | case square 19 | case diamond 20 | case underlinedBottom 21 | } 22 | 23 | /// Different input type for OTP fields. 24 | public enum KeyboardType: Int 25 | { 26 | case numeric 27 | case alphabet 28 | case alphaNumeric 29 | } 30 | 31 | public struct PinConfig 32 | { 33 | 34 | /// Define the display type for OTP fields. Defaults to `circular`. 35 | var otpFieldDisplayType: DisplayType = .circular 36 | 37 | /// Defines the number of OTP field needed. Defaults to 4. 38 | var otpFieldsCount: Int = 4 39 | 40 | /// Defines the type of the data that can be entered into OTP fields. Defaults to `numeric`. 41 | var otpFieldInputType: KeyboardType = .numeric 42 | 43 | /// Define the font to be used to OTP field. Defaults tp `systemFont` with size `20`. 44 | var otpFieldFont: UIFont = UIFont.systemFont(ofSize: 20) 45 | 46 | /// If set to `true`, then the content inside OTP field will be displayed in asterisk (*) format. Defaults to `false`. 47 | var otpFieldEntrySecureType: Bool = false 48 | 49 | /// If set to `true`, then the content inside OTP field will not be displayed. Instead whatever was set in `otpFieldEnteredBorderColor` will be used to mask the passcode. If `otpFieldEntrySecureType` is set to `true`, then it'll be ignored. This acts similar to Apple's lock code. Defaults to `false`. 50 | var otpFilledEntryDisplay: Bool = false 51 | 52 | /// If set to `false`, the blinking cursor for OTP field will not be visible. Defaults to `true`. 53 | var shouldRequireCursor: Bool = true 54 | 55 | /// If `shouldRequireCursor` is set to `false`, then this property will not have any effect. If `true`, then the color of cursor can be changed using this property. Defaults to `blue` color. 56 | var cursorColor: UIColor = UIColor.blue 57 | 58 | /// Defines the size of OTP field. Defaults to `60`. 59 | var otpFieldSize: CGFloat = 60 60 | 61 | /// Space between 2 OTP field. Defaults to `16`. 62 | var otpFieldSeparatorSpace: CGFloat = 16 63 | 64 | /// Border width to be used, if border is needed. Defaults to `2`. 65 | var otpFieldBorderWidth: CGFloat = 2 66 | 67 | /// If set, then editing can be done to intermediate fields even though previous fields are empty. Else editing will take place from last filled text field only. Defaults to `true`. 68 | var shouldAllowIntermediateEditing: Bool = true 69 | 70 | /// Set this value if a background color is needed when a text is not enetered in the OTP field. Defaults to `clear` color. 71 | var otpFieldDefaultBackgroundColor: UIColor = UIColor.clear 72 | 73 | /// Set this value if a background color is needed when a text is enetered in the OTP field. Defaults to `clear` color. 74 | var otpFieldEnteredBackgroundColor: UIColor = UIColor.clear 75 | 76 | /// Set this value if a border color is needed when a text is not enetered in the OTP field. Defaults to `black` color. 77 | var otpFieldDefaultBorderColor: UIColor = UIColor.black 78 | 79 | /// Set this value if a border color is needed when a text is enetered in the OTP field. Defaults to `black` color. 80 | var otpFieldEnteredBorderColor: UIColor = UIColor.black 81 | 82 | /// Optional value if a border color is needed when the otp entered is invalid/incorrect. 83 | var otpFieldErrorBorderColor: UIColor? 84 | 85 | 86 | init(otpFieldDisplayType: DisplayType = DisplayType.circular, 87 | otpFieldsCount: Int = 4, 88 | otpFieldInputType: KeyboardType = KeyboardType.numeric, 89 | otpFieldFont: UIFont = UIFont.systemFont(ofSize: 20), 90 | otpFieldEntrySecureType: Bool = false, 91 | otpFilledEntryDisplay: Bool = false, 92 | shouldRequireCursor: Bool = true, 93 | cursorColor: UIColor = UIColor.blue, 94 | otpFieldSize: CGFloat = 60, 95 | otpFieldSeparatorSpace: CGFloat = 16, 96 | otpFieldBorderWidth: CGFloat = 2, 97 | shouldAllowIntermediateEditing: Bool = true, 98 | otpFieldDefaultBackgroundColor: UIColor = UIColor.clear, 99 | otpFieldEnteredBackgroundColor: UIColor = UIColor.clear, 100 | otpFieldDefaultBorderColor: UIColor = UIColor.black, 101 | otpFieldEnteredBorderColor: UIColor = UIColor.black, 102 | otpFieldErrorBorderColor: UIColor = UIColor.red 103 | ) 104 | { 105 | 106 | self.otpFieldDisplayType = otpFieldDisplayType 107 | self.otpFieldsCount = otpFieldsCount 108 | self.otpFieldInputType = otpFieldInputType 109 | self.otpFieldFont = otpFieldFont 110 | self.otpFieldEntrySecureType = otpFieldEntrySecureType 111 | self.otpFilledEntryDisplay = otpFilledEntryDisplay 112 | self.shouldRequireCursor = shouldRequireCursor 113 | self.cursorColor = cursorColor 114 | self.otpFieldSize = otpFieldSize 115 | self.otpFieldSeparatorSpace = otpFieldSeparatorSpace 116 | self.shouldAllowIntermediateEditing = shouldAllowIntermediateEditing 117 | self.otpFieldDefaultBackgroundColor = otpFieldDefaultBackgroundColor 118 | self.otpFieldEnteredBackgroundColor = otpFieldEnteredBackgroundColor 119 | self.otpFieldDefaultBorderColor = otpFieldDefaultBorderColor 120 | self.otpFieldEnteredBorderColor = otpFieldEnteredBorderColor 121 | self.otpFieldErrorBorderColor = otpFieldErrorBorderColor 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /PHOTPView/PHOPTView/PHOTPTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PHOTPTextField.swift 3 | // PHOTPView 4 | // Version 1.0 5 | // 6 | // Created by Pankti Patel on 2019-08-13. 7 | // Copyright © 2019 Pankti Patel. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | class PHOTPTextField: UITextField 13 | { 14 | /// Border color info for field 15 | var borderColor: UIColor = UIColor.black 16 | 17 | /// Border width info for field 18 | var borderWidth: CGFloat = 2 19 | 20 | var shapeLayer: CAShapeLayer! 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | } 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | super.init(coder: aDecoder) 32 | } 33 | 34 | func initalizeUI(forFieldType type: DisplayType) 35 | { 36 | switch type 37 | { 38 | case .circular: 39 | layer.cornerRadius = bounds.size.width / 2 40 | case .square: 41 | layer.cornerRadius = 0 42 | case .diamond: 43 | addDiamondMask() 44 | case .underlinedBottom: 45 | addBottomView() 46 | } 47 | 48 | // Basic UI setup 49 | if type != .diamond && type != .underlinedBottom 50 | { 51 | layer.borderColor = borderColor.cgColor 52 | layer.borderWidth = borderWidth 53 | } 54 | 55 | autocorrectionType = .no 56 | textAlignment = .center 57 | } 58 | 59 | override func deleteBackward() 60 | { 61 | super.deleteBackward() 62 | 63 | _ = delegate?.textField?(self, shouldChangeCharactersIn: NSMakeRange(0, 0), replacementString: "") 64 | } 65 | 66 | // Helper function to create diamond view 67 | fileprivate func addDiamondMask() 68 | { 69 | let path = UIBezierPath() 70 | path.move(to: CGPoint(x: bounds.size.width / 2.0, y: 0)) 71 | path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height / 2.0)) 72 | path.addLine(to: CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height)) 73 | path.addLine(to: CGPoint(x: 0, y: bounds.size.height / 2.0)) 74 | path.close() 75 | 76 | let maskLayer = CAShapeLayer() 77 | maskLayer.path = path.cgPath 78 | 79 | layer.mask = maskLayer 80 | 81 | shapeLayer = CAShapeLayer() 82 | shapeLayer.path = path.cgPath 83 | shapeLayer.lineWidth = borderWidth 84 | shapeLayer.fillColor = backgroundColor?.cgColor 85 | shapeLayer.strokeColor = borderColor.cgColor 86 | 87 | layer.addSublayer(shapeLayer) 88 | } 89 | 90 | // Helper function to create a underlined bottom view 91 | fileprivate func addBottomView() 92 | { 93 | let path = UIBezierPath() 94 | path.move(to: CGPoint(x: 0, y: bounds.size.height)) 95 | path.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height)) 96 | path.close() 97 | 98 | shapeLayer = CAShapeLayer() 99 | shapeLayer.path = path.cgPath 100 | shapeLayer.lineWidth = borderWidth 101 | shapeLayer.fillColor = backgroundColor?.cgColor 102 | shapeLayer.strokeColor = borderColor.cgColor 103 | 104 | layer.addSublayer(shapeLayer) 105 | } 106 | } 107 | extension UIView 108 | { 109 | func shake() 110 | { 111 | let animation = CAKeyframeAnimation(keyPath: "transform.translation.x") 112 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 113 | animation.duration = 0.6 114 | animation.values = [-20.0, 20.0, -20.0, 20.0, -10.0, 10.0, -5.0, 5.0, 0.0 ] 115 | layer.add(animation, forKey: "shake") 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /PHOTPView/PHOPTView/PHOTPView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PHOTPView.swift 3 | // PHOTPView 4 | // Version 1.0 5 | // 6 | // Created by Pankti Patel on 2019-08-13. 7 | // Copyright © 2019 Pankti Patel. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | 12 | protocol PHOTPViewDelegate: class 13 | { 14 | /// Called whenever the textfield has to become first responder. Called for the first field when loading 15 | /// 16 | /// - Parameter index: the index of the field. Index starts from 0. 17 | /// - Returns: return true to show keyboard and vice versa 18 | func shouldBecomeFirstResponderForOTP(otpFieldIndex index: Int) -> Bool 19 | 20 | /// Called whenever all the OTP fields have been entered. It'll be called immediately after `hasEnteredAllOTP` delegate method is called. 21 | /// 22 | /// - Parameter otpString: The entered otp characters 23 | func getenteredOTP(otpString: String) 24 | 25 | /// Called whenever an OTP is entered. 26 | /// 27 | /// - Parameter hasEntered: `hasEntered` will be `true` if all the OTP fields have been filled. 28 | /// - Returns: return if OTP entered is valid or not. If false and all otp has been entered, then error 29 | func hasEnteredAllOTP(hasEntered: Bool) -> Bool 30 | 31 | } 32 | 33 | class PHOTPView: UIView 34 | { 35 | 36 | public var config : PinConfig! = PinConfig() 37 | 38 | weak var delegate: PHOTPViewDelegate? 39 | 40 | fileprivate var secureEntryData = [String]() 41 | 42 | convenience init(config:PinConfig = PinConfig()) 43 | { 44 | self.init() 45 | self.config = config 46 | 47 | } 48 | //MARK: Public functions 49 | /// Call this method to create the OTP field view. This method should be called at the last after necessary customization needed. If any property is modified at a later stage is simply ignored. 50 | func initializeUI() 51 | { 52 | layer.masksToBounds = true 53 | layoutIfNeeded() 54 | 55 | initializeOTPFields() 56 | 57 | // make first otp field as first responder 58 | (viewWithTag(1) as? PHOTPTextField)?.becomeFirstResponder() 59 | } 60 | 61 | //MARK: Private functions 62 | // Set up the fields 63 | private func initializeOTPFields() 64 | { 65 | secureEntryData.removeAll() 66 | 67 | for index in stride(from: 0, to: config.otpFieldsCount, by: 1) 68 | { 69 | let oldOtpField = viewWithTag(index + 1) as? PHOTPTextField 70 | oldOtpField?.removeFromSuperview() 71 | 72 | let otpField = getOTPtextField(forIndex: index) 73 | addSubview(otpField) 74 | 75 | secureEntryData.append("") 76 | } 77 | } 78 | 79 | // Initalize the required OTP fields 80 | private func getOTPtextField(forIndex index: Int) -> PHOTPTextField 81 | { 82 | let hasOddNumberOfFields = (config.otpFieldsCount % 2 == 1) 83 | var fieldFrame = CGRect(x: 0, y: 0, width: config.otpFieldSize, height: config.otpFieldSize) 84 | 85 | // If odd, then center of self will be center of middle field. If false, then center of self will be center of space between 2 middle fields. 86 | if hasOddNumberOfFields 87 | { 88 | // Calculate from middle each fields x and y values so as to align the entire view in center 89 | fieldFrame.origin.x = bounds.size.width / 2 - (CGFloat(config.otpFieldsCount / 2 - index) * (config.otpFieldSize + config.otpFieldSeparatorSpace) + config.otpFieldSize / 2) 90 | } 91 | else 92 | { 93 | // Calculate from middle each fields x and y values so as to align the entire view in center 94 | fieldFrame.origin.x = bounds.size.width / 2 - (CGFloat(config.otpFieldsCount / 2 - index) * config.otpFieldSize + CGFloat(config.otpFieldsCount / 2 - index - 1) * config.otpFieldSeparatorSpace + config.otpFieldSeparatorSpace / 2) 95 | } 96 | 97 | fieldFrame.origin.y = (bounds.size.height - config.otpFieldSize) / 2 98 | 99 | let otpField = PHOTPTextField(frame: fieldFrame) 100 | otpField.delegate = self 101 | otpField.tag = index + 1 102 | otpField.font = config.otpFieldFont 103 | 104 | // Set input type for OTP fields 105 | switch config.otpFieldInputType 106 | { 107 | case .numeric: 108 | otpField.keyboardType = .numberPad 109 | case .alphabet: 110 | otpField.keyboardType = .alphabet 111 | case .alphaNumeric: 112 | otpField.keyboardType = .namePhonePad 113 | } 114 | 115 | // Set the border values if needed 116 | otpField.borderColor = config.otpFieldDefaultBorderColor 117 | otpField.borderWidth = config.otpFieldBorderWidth 118 | 119 | if config.shouldRequireCursor 120 | { 121 | otpField.tintColor = config.cursorColor 122 | } 123 | else 124 | { 125 | otpField.tintColor = UIColor.clear 126 | } 127 | 128 | // Set the default background color when text not set 129 | otpField.backgroundColor = config.otpFieldDefaultBackgroundColor 130 | 131 | // Finally create the fields 132 | otpField.initalizeUI(forFieldType: config.otpFieldDisplayType) 133 | 134 | return otpField 135 | } 136 | 137 | // Check if previous text fields have been entered or not before textfield can edit the selected field. This will have effect only if 138 | private func isPreviousFieldsEntered(forTextField textField: UITextField) -> Bool 139 | { 140 | var isTextFilled = true 141 | var nextOTPField: UITextField? 142 | 143 | // If intermediate editing is not allowed, then check for last filled field in forward direction. 144 | if !config.shouldAllowIntermediateEditing 145 | { 146 | for index in stride(from: 1, to: config.otpFieldsCount + 1, by: 1) 147 | { 148 | let tempNextOTPField = viewWithTag(index) as? UITextField 149 | 150 | if let tempNextOTPFieldText = tempNextOTPField?.text, tempNextOTPFieldText.isEmpty 151 | { 152 | nextOTPField = tempNextOTPField 153 | break 154 | } 155 | } 156 | 157 | if let nextOTPField = nextOTPField 158 | { 159 | isTextFilled = (nextOTPField == textField || (textField.tag) == (nextOTPField.tag - 1)) 160 | } 161 | } 162 | 163 | return isTextFilled 164 | } 165 | 166 | // Helper function to get the OTP String entered 167 | private func reloadEnteredOTPSTring(isDeleted: Bool) 168 | { 169 | if isDeleted 170 | { 171 | _ = delegate?.hasEnteredAllOTP(hasEntered: false) 172 | 173 | // Set the default enteres state for otp entry 174 | for index in stride(from: 0, to: config.otpFieldsCount, by: 1) 175 | { 176 | var otpField = viewWithTag(index + 1) as? PHOTPTextField 177 | 178 | if otpField == nil 179 | { 180 | otpField = getOTPtextField(forIndex: index) 181 | } 182 | 183 | let fieldBackgroundColor = (otpField?.text ?? "").isEmpty ? config.otpFieldDefaultBackgroundColor : config.otpFieldEnteredBackgroundColor 184 | let fieldBorderColor = (otpField?.text ?? "").isEmpty ? config.otpFieldDefaultBorderColor : config.otpFieldEnteredBorderColor 185 | 186 | if config.otpFieldDisplayType == .diamond || config.otpFieldDisplayType == .underlinedBottom 187 | { 188 | otpField?.shapeLayer.fillColor = fieldBackgroundColor.cgColor 189 | otpField?.shapeLayer.strokeColor = fieldBorderColor.cgColor 190 | } 191 | else 192 | { 193 | otpField?.backgroundColor = fieldBackgroundColor 194 | otpField?.layer.borderColor = fieldBorderColor.cgColor 195 | } 196 | } 197 | } 198 | else 199 | { 200 | var enteredOTPString = "" 201 | 202 | // Check for entered OTP 203 | for index in stride(from: 0, to: secureEntryData.count, by: 1) 204 | { 205 | if !secureEntryData[index].isEmpty { 206 | enteredOTPString.append(secureEntryData[index]) 207 | } 208 | } 209 | 210 | if enteredOTPString.count == config.otpFieldsCount 211 | { 212 | delegate?.getenteredOTP(otpString: enteredOTPString) 213 | 214 | // Check if all OTP fields have been filled or not. Based on that call the 2 delegate methods. 215 | let isValid = delegate?.hasEnteredAllOTP(hasEntered: (enteredOTPString.count == config.otpFieldsCount)) ?? false 216 | 217 | // Set the error state for invalid otp entry 218 | for index in stride(from: 0, to: config.otpFieldsCount, by: 1) 219 | { 220 | var otpField = viewWithTag(index + 1) as? PHOTPTextField 221 | 222 | if otpField == nil 223 | { 224 | otpField = getOTPtextField(forIndex: index) 225 | } 226 | 227 | if !isValid 228 | { 229 | // Set error border color if set, if not, set default border color 230 | otpField?.layer.borderColor = (config.otpFieldErrorBorderColor ?? config.otpFieldEnteredBorderColor).cgColor 231 | otpField?.shake() 232 | } 233 | else 234 | { 235 | otpField?.layer.borderColor = config.otpFieldEnteredBorderColor.cgColor 236 | } 237 | } 238 | } 239 | } 240 | } 241 | } 242 | 243 | extension PHOTPView: UITextFieldDelegate 244 | { 245 | func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool 246 | { 247 | let shouldBeginEditing = delegate?.shouldBecomeFirstResponderForOTP(otpFieldIndex: (textField.tag - 1)) ?? true 248 | if shouldBeginEditing 249 | { 250 | return isPreviousFieldsEntered(forTextField: textField) 251 | } 252 | 253 | return shouldBeginEditing 254 | } 255 | 256 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool 257 | { 258 | let replacedText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) ?? "" 259 | 260 | // Check since only alphabet keyboard is not available in iOS 261 | if !replacedText.isEmpty && config.otpFieldInputType == .alphabet && replacedText.rangeOfCharacter(from: .letters) == nil { 262 | return false 263 | } 264 | 265 | if replacedText.count >= 1 { 266 | // If field has a text already, then replace the text and move to next field if present 267 | secureEntryData[textField.tag - 1] = string 268 | 269 | if config.otpFilledEntryDisplay 270 | { 271 | textField.text = " " 272 | } 273 | else 274 | { 275 | if config.otpFieldEntrySecureType 276 | { 277 | textField.text = "*" 278 | } 279 | else 280 | { 281 | textField.text = string 282 | } 283 | } 284 | 285 | if config.otpFieldDisplayType == .diamond || config.otpFieldDisplayType == .underlinedBottom 286 | { 287 | (textField as! PHOTPTextField).shapeLayer.fillColor = config.otpFieldEnteredBackgroundColor.cgColor 288 | (textField as! PHOTPTextField).shapeLayer.strokeColor = config.otpFieldEnteredBorderColor.cgColor 289 | } 290 | else 291 | { 292 | textField.backgroundColor = config.otpFieldEnteredBackgroundColor 293 | textField.layer.borderColor = config.otpFieldEnteredBorderColor.cgColor 294 | } 295 | 296 | let nextOTPField = viewWithTag(textField.tag + 1) 297 | 298 | if let nextOTPField = nextOTPField 299 | { 300 | nextOTPField.becomeFirstResponder() 301 | } 302 | else 303 | { 304 | textField.resignFirstResponder() 305 | } 306 | 307 | // Get the entered string 308 | reloadEnteredOTPSTring(isDeleted: false) 309 | } 310 | else 311 | { 312 | let currentText = textField.text ?? "" 313 | 314 | if textField.tag > 1 && currentText.isEmpty 315 | { 316 | if let prevOTPField = viewWithTag(textField.tag - 1) as? UITextField 317 | { 318 | deleteText(in: prevOTPField) 319 | } 320 | } 321 | else 322 | { 323 | deleteText(in: textField) 324 | 325 | if textField.tag > 1 326 | { 327 | if let prevOTPField = viewWithTag(textField.tag - 1) as? UITextField 328 | { 329 | prevOTPField.becomeFirstResponder() 330 | } 331 | } 332 | } 333 | } 334 | 335 | return false 336 | } 337 | 338 | private func deleteText(in textField: UITextField) 339 | { 340 | // If deleting the text, then move to previous text field if present 341 | secureEntryData[textField.tag - 1] = "" 342 | textField.text = "" 343 | 344 | if config.otpFieldDisplayType == .diamond || config.otpFieldDisplayType == .underlinedBottom 345 | { 346 | (textField as! PHOTPTextField).shapeLayer.fillColor = config.otpFieldDefaultBackgroundColor.cgColor 347 | (textField as! PHOTPTextField).shapeLayer.strokeColor = config.otpFieldDefaultBorderColor.cgColor 348 | } 349 | else 350 | { 351 | textField.backgroundColor = config.otpFieldDefaultBackgroundColor 352 | textField.layer.borderColor = config.otpFieldDefaultBorderColor.cgColor 353 | } 354 | 355 | textField.becomeFirstResponder() 356 | 357 | // Get the entered string 358 | reloadEnteredOTPSTring(isDeleted: true) 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHOTPView 2 | 3 | Fully Customized pin code(OTP) verification view without storyboard. 4 | 5 | 6 | ## Getting Started 7 | 8 | ### Features 9 | 10 | - Flawless focus change to the consecutive OTP box when the text is entered/deleted. 11 | - When the user taps on the Pinview, the first empty box available is focused automatically (when the cursor is hidden). 12 | - Customisations are available for pin box sizes, font color, border color, inputType etc. 13 | 14 | ### Installation 15 | 16 | download project for quick demo. 17 | 18 | Manually : 19 | 20 | - Copy and drag the PHOTPView/ folder to your project. 21 | 22 | ### How to Use? 23 | 24 | - create a PHOTPView object 25 | 26 | ```sh 27 | var otpView: PHOTPView! 28 | 29 | ``` 30 | 31 | - create a PinConfig object 32 | 33 | ```sh 34 | var config : PinConfig = PinConfig() 35 | ``` 36 | 37 | responsible for all kind of customization 38 | #### Example: 39 | 40 | ```sh 41 | config.otpFieldDisplayType = .square 42 | config.shouldAllowIntermediateEditing = false 43 | config.otpFieldDefaultBorderColor = UIColor.blue 44 | config.otpFieldEnteredBorderColor = UIColor.green 45 | config.otpFieldErrorBorderColor = UIColor.red 46 | ``` 47 | 48 | - initialize view by assigning cofig object 49 | 50 | ```sh 51 | otpView = PHOTPView(config: config) 52 | otpView.delegate = self 53 | ``` 54 | 55 | - assign delegate 56 | 57 | ```sh 58 | otpView.delegate = self 59 | ``` 60 | 61 | - Finally, Add to your view in which you want to configure OTPview 62 | ```sh 63 | self.view.addSubview(otpView) 64 | ``` 65 | 66 | ##### NOTE: Do not forget to initialize UI by calling below method. 67 | 68 | ```sh 69 | otpView.initializeUI() 70 | ``` 71 | 72 | # Prerequisites: 73 | - xcode 9 74 | - swift 5 75 | 76 | 77 | 78 | License 79 | ---- 80 | 81 | MIT 82 | --------------------------------------------------------------------------------