├── .github └── FUNDING.yml ├── .gitignore ├── .swift-version ├── .travis.yml ├── KAPinField.podspec ├── KAPinField.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── KAPinField iOS.xcscheme │ └── KAPinField.xcscheme ├── KAPinField.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── KAPinField ├── Example │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift └── Sources │ ├── Info.plist │ ├── KAPinField.h │ └── KAPinField.swift ├── LICENSE ├── Package.swift ├── README.md ├── preview1.gif └── preview2.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [kirualex] 2 | custom: ['https://www.paypal.me/alexiscreuzot'] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | .DS_Store 70 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.4 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode10 3 | 4 | branches: 5 | only: 6 | - master 7 | 8 | script: 9 | - xcodebuild build -project KAPinField.xcodeproj -scheme KAPinField -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone X,OS=12' | xcpretty 10 | -------------------------------------------------------------------------------- /KAPinField.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'KAPinField' 3 | s.version = '5.0.3' 4 | s.summary = 'Lightweight, highly customizable Pin Code Field library for iOS, written in Swift' 5 | s.homepage = 'https://github.com/kirualex/KAPinField' 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "Alexis Creuzot" => "alexis.creuzot@gmail.com" } 8 | s.source = { :git => "https://github.com/kirualex/KAPinField.git", :tag => s.version.to_s } 9 | s.platform = :ios, '9.0' 10 | s.source_files = '**/KAPinField.swift' 11 | end 12 | -------------------------------------------------------------------------------- /KAPinField.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8BA8433222667EE000CC8D98 /* KAPinField.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BA8432B22667EE000CC8D98 /* KAPinField.framework */; }; 11 | 8BA8433322667EE000CC8D98 /* KAPinField.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8BA8432B22667EE000CC8D98 /* KAPinField.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12 | 8BA843382266800600CC8D98 /* KAPinField.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BA8432D22667EE000CC8D98 /* KAPinField.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 8BA843392266803200CC8D98 /* KAPinField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAE9C7121749E5200ACE4E6 /* KAPinField.swift */; }; 14 | FAAE9C6021749E0200ACE4E6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAE9C5F21749E0200ACE4E6 /* AppDelegate.swift */; }; 15 | FAAE9C6221749E0200ACE4E6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAE9C6121749E0200ACE4E6 /* ViewController.swift */; }; 16 | FAAE9C6521749E0200ACE4E6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAAE9C6321749E0200ACE4E6 /* Main.storyboard */; }; 17 | FAAE9C6721749E0300ACE4E6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FAAE9C6621749E0300ACE4E6 /* Assets.xcassets */; }; 18 | FAAE9C6A21749E0300ACE4E6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAAE9C6821749E0300ACE4E6 /* LaunchScreen.storyboard */; }; 19 | FAAE9C7221749E5200ACE4E6 /* KAPinField.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAE9C7121749E5200ACE4E6 /* KAPinField.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 8BA8433022667EE000CC8D98 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = FAAE9C5421749E0100ACE4E6 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 8BA8432A22667EE000CC8D98; 28 | remoteInfo = "KAPinField iOS"; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | 8BA8433722667EE000CC8D98 /* Embed Frameworks */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = ""; 37 | dstSubfolderSpec = 10; 38 | files = ( 39 | 8BA8433322667EE000CC8D98 /* KAPinField.framework in Embed Frameworks */, 40 | ); 41 | name = "Embed Frameworks"; 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 8BA8432B22667EE000CC8D98 /* KAPinField.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KAPinField.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 8BA8432D22667EE000CC8D98 /* KAPinField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KAPinField.h; sourceTree = ""; }; 49 | 8BA8432E22667EE000CC8D98 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | FAAE9C5C21749E0100ACE4E6 /* KAPinField.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KAPinField.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | FAAE9C5F21749E0200ACE4E6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | FAAE9C6121749E0200ACE4E6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 53 | FAAE9C6421749E0200ACE4E6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | FAAE9C6621749E0300ACE4E6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | FAAE9C6921749E0300ACE4E6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | FAAE9C6B21749E0300ACE4E6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | FAAE9C7121749E5200ACE4E6 /* KAPinField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KAPinField.swift; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 8BA8432822667EE000CC8D98 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | FAAE9C5921749E0100ACE4E6 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | 8BA8433222667EE000CC8D98 /* KAPinField.framework in Frameworks */, 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | FA88CCEC2174FA7C00EFDA17 /* Sources */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | FAAE9C7121749E5200ACE4E6 /* KAPinField.swift */, 83 | 8BA8432D22667EE000CC8D98 /* KAPinField.h */, 84 | 8BA8432E22667EE000CC8D98 /* Info.plist */, 85 | ); 86 | path = Sources; 87 | sourceTree = ""; 88 | }; 89 | FA88CCED2174FA8600EFDA17 /* Example */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | FAAE9C5F21749E0200ACE4E6 /* AppDelegate.swift */, 93 | FAAE9C6121749E0200ACE4E6 /* ViewController.swift */, 94 | FAAE9C6321749E0200ACE4E6 /* Main.storyboard */, 95 | FAAE9C6621749E0300ACE4E6 /* Assets.xcassets */, 96 | FAAE9C6821749E0300ACE4E6 /* LaunchScreen.storyboard */, 97 | FAAE9C6B21749E0300ACE4E6 /* Info.plist */, 98 | ); 99 | path = Example; 100 | sourceTree = ""; 101 | }; 102 | FAAE9C5321749E0100ACE4E6 = { 103 | isa = PBXGroup; 104 | children = ( 105 | FAAE9C5E21749E0200ACE4E6 /* KAPinField */, 106 | FAAE9C5D21749E0100ACE4E6 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | FAAE9C5D21749E0100ACE4E6 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | FAAE9C5C21749E0100ACE4E6 /* KAPinField.app */, 114 | 8BA8432B22667EE000CC8D98 /* KAPinField.framework */, 115 | ); 116 | name = Products; 117 | sourceTree = ""; 118 | }; 119 | FAAE9C5E21749E0200ACE4E6 /* KAPinField */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | FA88CCEC2174FA7C00EFDA17 /* Sources */, 123 | FA88CCED2174FA8600EFDA17 /* Example */, 124 | ); 125 | path = KAPinField; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXHeadersBuildPhase section */ 131 | 8BA8432622667EE000CC8D98 /* Headers */ = { 132 | isa = PBXHeadersBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 8BA843382266800600CC8D98 /* KAPinField.h in Headers */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXHeadersBuildPhase section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | 8BA8432A22667EE000CC8D98 /* KAPinField iOS */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 8BA8433622667EE000CC8D98 /* Build configuration list for PBXNativeTarget "KAPinField iOS" */; 145 | buildPhases = ( 146 | 8BA8432622667EE000CC8D98 /* Headers */, 147 | 8BA8432722667EE000CC8D98 /* Sources */, 148 | 8BA8432822667EE000CC8D98 /* Frameworks */, 149 | 8BA8432922667EE000CC8D98 /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = "KAPinField iOS"; 156 | productName = "KAPinField iOS"; 157 | productReference = 8BA8432B22667EE000CC8D98 /* KAPinField.framework */; 158 | productType = "com.apple.product-type.framework"; 159 | }; 160 | FAAE9C5B21749E0100ACE4E6 /* KAPinField */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = FAAE9C6E21749E0300ACE4E6 /* Build configuration list for PBXNativeTarget "KAPinField" */; 163 | buildPhases = ( 164 | FAAE9C5821749E0100ACE4E6 /* Sources */, 165 | FAAE9C5921749E0100ACE4E6 /* Frameworks */, 166 | FAAE9C5A21749E0100ACE4E6 /* Resources */, 167 | 8BA8433722667EE000CC8D98 /* Embed Frameworks */, 168 | ); 169 | buildRules = ( 170 | ); 171 | dependencies = ( 172 | 8BA8433122667EE000CC8D98 /* PBXTargetDependency */, 173 | ); 174 | name = KAPinField; 175 | productName = KAPinCode; 176 | productReference = FAAE9C5C21749E0100ACE4E6 /* KAPinField.app */; 177 | productType = "com.apple.product-type.application"; 178 | }; 179 | /* End PBXNativeTarget section */ 180 | 181 | /* Begin PBXProject section */ 182 | FAAE9C5421749E0100ACE4E6 /* Project object */ = { 183 | isa = PBXProject; 184 | attributes = { 185 | LastSwiftUpdateCheck = 1000; 186 | LastUpgradeCheck = 1200; 187 | ORGANIZATIONNAME = alexiscreuzot; 188 | TargetAttributes = { 189 | 8BA8432A22667EE000CC8D98 = { 190 | CreatedOnToolsVersion = 10.2; 191 | }; 192 | FAAE9C5B21749E0100ACE4E6 = { 193 | CreatedOnToolsVersion = 10.0; 194 | LastSwiftMigration = 1020; 195 | }; 196 | }; 197 | }; 198 | buildConfigurationList = FAAE9C5721749E0100ACE4E6 /* Build configuration list for PBXProject "KAPinField" */; 199 | compatibilityVersion = "Xcode 9.3"; 200 | developmentRegion = en; 201 | hasScannedForEncodings = 0; 202 | knownRegions = ( 203 | en, 204 | Base, 205 | ); 206 | mainGroup = FAAE9C5321749E0100ACE4E6; 207 | productRefGroup = FAAE9C5D21749E0100ACE4E6 /* Products */; 208 | projectDirPath = ""; 209 | projectRoot = ""; 210 | targets = ( 211 | FAAE9C5B21749E0100ACE4E6 /* KAPinField */, 212 | 8BA8432A22667EE000CC8D98 /* KAPinField iOS */, 213 | ); 214 | }; 215 | /* End PBXProject section */ 216 | 217 | /* Begin PBXResourcesBuildPhase section */ 218 | 8BA8432922667EE000CC8D98 /* Resources */ = { 219 | isa = PBXResourcesBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | FAAE9C5A21749E0100ACE4E6 /* Resources */ = { 226 | isa = PBXResourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | FAAE9C6A21749E0300ACE4E6 /* LaunchScreen.storyboard in Resources */, 230 | FAAE9C6721749E0300ACE4E6 /* Assets.xcassets in Resources */, 231 | FAAE9C6521749E0200ACE4E6 /* Main.storyboard in Resources */, 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | /* End PBXResourcesBuildPhase section */ 236 | 237 | /* Begin PBXSourcesBuildPhase section */ 238 | 8BA8432722667EE000CC8D98 /* Sources */ = { 239 | isa = PBXSourcesBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | 8BA843392266803200CC8D98 /* KAPinField.swift in Sources */, 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | }; 246 | FAAE9C5821749E0100ACE4E6 /* Sources */ = { 247 | isa = PBXSourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | FAAE9C6221749E0200ACE4E6 /* ViewController.swift in Sources */, 251 | FAAE9C7221749E5200ACE4E6 /* KAPinField.swift in Sources */, 252 | FAAE9C6021749E0200ACE4E6 /* AppDelegate.swift in Sources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXSourcesBuildPhase section */ 257 | 258 | /* Begin PBXTargetDependency section */ 259 | 8BA8433122667EE000CC8D98 /* PBXTargetDependency */ = { 260 | isa = PBXTargetDependency; 261 | target = 8BA8432A22667EE000CC8D98 /* KAPinField iOS */; 262 | targetProxy = 8BA8433022667EE000CC8D98 /* PBXContainerItemProxy */; 263 | }; 264 | /* End PBXTargetDependency section */ 265 | 266 | /* Begin PBXVariantGroup section */ 267 | FAAE9C6321749E0200ACE4E6 /* Main.storyboard */ = { 268 | isa = PBXVariantGroup; 269 | children = ( 270 | FAAE9C6421749E0200ACE4E6 /* Base */, 271 | ); 272 | name = Main.storyboard; 273 | sourceTree = ""; 274 | }; 275 | FAAE9C6821749E0300ACE4E6 /* LaunchScreen.storyboard */ = { 276 | isa = PBXVariantGroup; 277 | children = ( 278 | FAAE9C6921749E0300ACE4E6 /* Base */, 279 | ); 280 | name = LaunchScreen.storyboard; 281 | sourceTree = ""; 282 | }; 283 | /* End PBXVariantGroup section */ 284 | 285 | /* Begin XCBuildConfiguration section */ 286 | 8BA8433422667EE000CC8D98 /* Debug */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | CODE_SIGN_IDENTITY = ""; 290 | CODE_SIGN_STYLE = Manual; 291 | CURRENT_PROJECT_VERSION = 1; 292 | DEFINES_MODULE = YES; 293 | DEVELOPMENT_TEAM = ""; 294 | DYLIB_COMPATIBILITY_VERSION = 1; 295 | DYLIB_CURRENT_VERSION = 1; 296 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 297 | INFOPLIST_FILE = KAPinField/Sources/Info.plist; 298 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 299 | LD_RUNPATH_SEARCH_PATHS = ( 300 | "$(inherited)", 301 | "@executable_path/Frameworks", 302 | "@loader_path/Frameworks", 303 | ); 304 | PRODUCT_BUNDLE_IDENTIFIER = "com.alexiscreuzot.KAPinField-iOS"; 305 | PRODUCT_NAME = KAPinField; 306 | PROVISIONING_PROFILE_SPECIFIER = ""; 307 | SKIP_INSTALL = YES; 308 | SWIFT_VERSION = 5.0; 309 | TARGETED_DEVICE_FAMILY = "1,2"; 310 | VERSIONING_SYSTEM = "apple-generic"; 311 | VERSION_INFO_PREFIX = ""; 312 | }; 313 | name = Debug; 314 | }; 315 | 8BA8433522667EE000CC8D98 /* Release */ = { 316 | isa = XCBuildConfiguration; 317 | buildSettings = { 318 | CODE_SIGN_IDENTITY = ""; 319 | CODE_SIGN_STYLE = Manual; 320 | CURRENT_PROJECT_VERSION = 1; 321 | DEFINES_MODULE = YES; 322 | DEVELOPMENT_TEAM = ""; 323 | DYLIB_COMPATIBILITY_VERSION = 1; 324 | DYLIB_CURRENT_VERSION = 1; 325 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 326 | INFOPLIST_FILE = KAPinField/Sources/Info.plist; 327 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@executable_path/Frameworks", 331 | "@loader_path/Frameworks", 332 | ); 333 | PRODUCT_BUNDLE_IDENTIFIER = "com.alexiscreuzot.KAPinField-iOS"; 334 | PRODUCT_NAME = KAPinField; 335 | PROVISIONING_PROFILE_SPECIFIER = ""; 336 | SKIP_INSTALL = YES; 337 | SWIFT_VERSION = 5.0; 338 | TARGETED_DEVICE_FAMILY = "1,2"; 339 | VERSIONING_SYSTEM = "apple-generic"; 340 | VERSION_INFO_PREFIX = ""; 341 | }; 342 | name = Release; 343 | }; 344 | FAAE9C6C21749E0300ACE4E6 /* Debug */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ALWAYS_SEARCH_USER_PATHS = NO; 348 | CLANG_ANALYZER_NONNULL = YES; 349 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 351 | CLANG_CXX_LIBRARY = "libc++"; 352 | CLANG_ENABLE_MODULES = YES; 353 | CLANG_ENABLE_OBJC_ARC = YES; 354 | CLANG_ENABLE_OBJC_WEAK = YES; 355 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 356 | CLANG_WARN_BOOL_CONVERSION = YES; 357 | CLANG_WARN_COMMA = YES; 358 | CLANG_WARN_CONSTANT_CONVERSION = YES; 359 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 360 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 361 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 362 | CLANG_WARN_EMPTY_BODY = YES; 363 | CLANG_WARN_ENUM_CONVERSION = YES; 364 | CLANG_WARN_INFINITE_RECURSION = YES; 365 | CLANG_WARN_INT_CONVERSION = YES; 366 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 367 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 368 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 370 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 371 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 372 | CLANG_WARN_STRICT_PROTOTYPES = YES; 373 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 374 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 375 | CLANG_WARN_UNREACHABLE_CODE = YES; 376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 377 | CODE_SIGN_IDENTITY = "iPhone Developer"; 378 | COPY_PHASE_STRIP = NO; 379 | DEBUG_INFORMATION_FORMAT = dwarf; 380 | ENABLE_STRICT_OBJC_MSGSEND = YES; 381 | ENABLE_TESTABILITY = YES; 382 | GCC_C_LANGUAGE_STANDARD = gnu11; 383 | GCC_DYNAMIC_NO_PIC = NO; 384 | GCC_NO_COMMON_BLOCKS = YES; 385 | GCC_OPTIMIZATION_LEVEL = 0; 386 | GCC_PREPROCESSOR_DEFINITIONS = ( 387 | "DEBUG=1", 388 | "$(inherited)", 389 | ); 390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 392 | GCC_WARN_UNDECLARED_SELECTOR = YES; 393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 394 | GCC_WARN_UNUSED_FUNCTION = YES; 395 | GCC_WARN_UNUSED_VARIABLE = YES; 396 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 397 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 398 | MTL_FAST_MATH = YES; 399 | ONLY_ACTIVE_ARCH = YES; 400 | SDKROOT = iphoneos; 401 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 403 | SWIFT_VERSION = 4.2; 404 | }; 405 | name = Debug; 406 | }; 407 | FAAE9C6D21749E0300ACE4E6 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ALWAYS_SEARCH_USER_PATHS = NO; 411 | CLANG_ANALYZER_NONNULL = YES; 412 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 413 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 414 | CLANG_CXX_LIBRARY = "libc++"; 415 | CLANG_ENABLE_MODULES = YES; 416 | CLANG_ENABLE_OBJC_ARC = YES; 417 | CLANG_ENABLE_OBJC_WEAK = YES; 418 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 419 | CLANG_WARN_BOOL_CONVERSION = YES; 420 | CLANG_WARN_COMMA = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 423 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 424 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 425 | CLANG_WARN_EMPTY_BODY = YES; 426 | CLANG_WARN_ENUM_CONVERSION = YES; 427 | CLANG_WARN_INFINITE_RECURSION = YES; 428 | CLANG_WARN_INT_CONVERSION = YES; 429 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 430 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 431 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 433 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 434 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 435 | CLANG_WARN_STRICT_PROTOTYPES = YES; 436 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 437 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 438 | CLANG_WARN_UNREACHABLE_CODE = YES; 439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 440 | CODE_SIGN_IDENTITY = "iPhone Developer"; 441 | COPY_PHASE_STRIP = NO; 442 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | ENABLE_STRICT_OBJC_MSGSEND = YES; 445 | GCC_C_LANGUAGE_STANDARD = gnu11; 446 | GCC_NO_COMMON_BLOCKS = YES; 447 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 448 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 449 | GCC_WARN_UNDECLARED_SELECTOR = YES; 450 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 451 | GCC_WARN_UNUSED_FUNCTION = YES; 452 | GCC_WARN_UNUSED_VARIABLE = YES; 453 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 454 | MTL_ENABLE_DEBUG_INFO = NO; 455 | MTL_FAST_MATH = YES; 456 | SDKROOT = iphoneos; 457 | SWIFT_COMPILATION_MODE = wholemodule; 458 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 459 | SWIFT_VERSION = 4.2; 460 | VALIDATE_PRODUCT = YES; 461 | }; 462 | name = Release; 463 | }; 464 | FAAE9C6F21749E0300ACE4E6 /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 468 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 469 | CODE_SIGN_STYLE = Automatic; 470 | DEVELOPMENT_TEAM = J6US63N7X8; 471 | INFOPLIST_FILE = "$(SRCROOT)/KAPinField/Example/Info.plist"; 472 | LD_RUNPATH_SEARCH_PATHS = ( 473 | "$(inherited)", 474 | "@executable_path/Frameworks", 475 | ); 476 | PRODUCT_BUNDLE_IDENTIFIER = com.alexiscreuzot.KAPinField; 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | SWIFT_VERSION = 5.0; 479 | TARGETED_DEVICE_FAMILY = "1,2"; 480 | }; 481 | name = Debug; 482 | }; 483 | FAAE9C7021749E0300ACE4E6 /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 487 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 488 | CODE_SIGN_STYLE = Automatic; 489 | DEVELOPMENT_TEAM = J6US63N7X8; 490 | INFOPLIST_FILE = "$(SRCROOT)/KAPinField/Example/Info.plist"; 491 | LD_RUNPATH_SEARCH_PATHS = ( 492 | "$(inherited)", 493 | "@executable_path/Frameworks", 494 | ); 495 | PRODUCT_BUNDLE_IDENTIFIER = com.alexiscreuzot.KAPinField; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SWIFT_VERSION = 5.0; 498 | TARGETED_DEVICE_FAMILY = "1,2"; 499 | }; 500 | name = Release; 501 | }; 502 | /* End XCBuildConfiguration section */ 503 | 504 | /* Begin XCConfigurationList section */ 505 | 8BA8433622667EE000CC8D98 /* Build configuration list for PBXNativeTarget "KAPinField iOS" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 8BA8433422667EE000CC8D98 /* Debug */, 509 | 8BA8433522667EE000CC8D98 /* Release */, 510 | ); 511 | defaultConfigurationIsVisible = 0; 512 | defaultConfigurationName = Release; 513 | }; 514 | FAAE9C5721749E0100ACE4E6 /* Build configuration list for PBXProject "KAPinField" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | FAAE9C6C21749E0300ACE4E6 /* Debug */, 518 | FAAE9C6D21749E0300ACE4E6 /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | FAAE9C6E21749E0300ACE4E6 /* Build configuration list for PBXNativeTarget "KAPinField" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | FAAE9C6F21749E0300ACE4E6 /* Debug */, 527 | FAAE9C7021749E0300ACE4E6 /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | /* End XCConfigurationList section */ 533 | }; 534 | rootObject = FAAE9C5421749E0100ACE4E6 /* Project object */; 535 | } 536 | -------------------------------------------------------------------------------- /KAPinField.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KAPinField.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KAPinField.xcodeproj/xcshareddata/xcschemes/KAPinField iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /KAPinField.xcodeproj/xcshareddata/xcschemes/KAPinField.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /KAPinField.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KAPinField.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /KAPinField/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // KAPinCode 4 | // 5 | // Created by Alexis Creuzot on 15/10/2018. 6 | // Copyright © 2018 alexiscreuzot. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /KAPinField/Example/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 | } -------------------------------------------------------------------------------- /KAPinField/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /KAPinField/Example/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 | 27 | 28 | 29 | 30 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /KAPinField/Example/Base.lproj/Main.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 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /KAPinField/Example/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 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarStyle 32 | UIStatusBarStyleLightContent 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /KAPinField/Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // KAPinCode 4 | // 5 | // Created by Alexis Creuzot on 15/10/2018. 6 | // Copyright © 2018 alexiscreuzot. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet var secureSwitch: UISwitch! 14 | @IBOutlet var secureLabel: UILabel! 15 | @IBOutlet var targetCodeLabel: UILabel! 16 | @IBOutlet var pinField: KAPinField! 17 | @IBOutlet var refreshButton: UIButton! 18 | 19 | @IBOutlet var keyboardheightConstraint: NSLayoutConstraint! 20 | 21 | private var targetCode = "" 22 | 23 | override var preferredStatusBarStyle: UIStatusBarStyle { 24 | return .lightContent 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | // -- Properties -- 31 | self.setupPinfield() 32 | 33 | // Get focus 34 | pinField.becomeFirstResponder() 35 | } 36 | 37 | 38 | func randomCode(numDigits: Int) -> String { 39 | var string = "" 40 | for _ in 0.. 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 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /KAPinField/Sources/KAPinField.h: -------------------------------------------------------------------------------- 1 | // 2 | // KAPinField.h 3 | // KAPinField 4 | // 5 | // Created by Basem Emara on 2019-04-16. 6 | // Copyright © 2019 alexiscreuzot. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for KAPinField. 12 | FOUNDATION_EXPORT double KAPinFieldVersionNumber; 13 | 14 | //! Project version string for KAPinField. 15 | FOUNDATION_EXPORT const unsigned char KAPinFieldVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /KAPinField/Sources/KAPinField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KAPinField.swift 3 | // KAPinCode 4 | // 5 | // Created by Alexis Creuzot on 15/10/2018. 6 | // Copyright © 2018 alexiscreuzot. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // Mark: - KAPinFieldDelegate 12 | public protocol KAPinFieldDelegate : AnyObject { 13 | func pinField(_ field: KAPinField, didChangeTo string: String, isValid: Bool) // Optional 14 | func pinField(_ field: KAPinField, didFinishWith code: String) 15 | } 16 | 17 | public extension KAPinFieldDelegate { 18 | func pinField(_ field: KAPinField, didChangeTo string: String, isValid: Bool) {} 19 | } 20 | 21 | public struct KAPinFieldProperties { 22 | public weak var delegate : KAPinFieldDelegate? = nil 23 | public var numberOfCharacters: Int = 4 { 24 | didSet { 25 | precondition(numberOfCharacters >= 0, "🚫 Number of character must be >= 0, with 0 meaning dynamic") 26 | } 27 | } 28 | public var validCharacters: String = "0123456789" { 29 | didSet { 30 | precondition(validCharacters.count > 0, "🚫 There must be at least 1 valid character") 31 | precondition(!validCharacters.contains(token), "🚫 Valid characters can't contain token \"\(token)\"") 32 | } 33 | } 34 | public var token: Character = "•" { 35 | didSet { 36 | precondition(!validCharacters.contains(token), "🚫 token can't be one of the valid characters \"\(token)\"") 37 | precondition(!token.isWhitespace, "🚫 token can't be a whitespace. Please use a token with a clear color to achieve the same effect") 38 | } 39 | } 40 | public var animateFocus : Bool = true 41 | public var isSecure : Bool = false 42 | public var secureToken: Character = "•" 43 | public var isUppercased: Bool = false 44 | public var keyboardType: UIKeyboardType = .numberPad 45 | } 46 | 47 | public struct KAPinFieldAppearance { 48 | 49 | public init() {} 50 | 51 | public var font : KA_MonospacedFont? = .menlo(40) 52 | public var tokenColor : UIColor = .black 53 | public var tokenFocusColor : UIColor = .gray 54 | public var textColor : UIColor = .black 55 | public var kerning : CGFloat = 20.0 56 | public var backColor : UIColor = UIColor.clear 57 | public var backBorderColor : UIColor = UIColor.clear 58 | public var backBorderWidth : CGFloat = 1 59 | public var backCornerRadius : CGFloat = 4 60 | public var backOffset : CGFloat = 4 61 | public var backFocusColor : UIColor = .clear 62 | public var backBorderFocusColor : UIColor = .black 63 | public var backActiveColor : UIColor = .clear 64 | public var backBorderActiveColor : UIColor = .black 65 | public var backRounded : Bool = false 66 | } 67 | 68 | // Mark: - KAPinField Class 69 | public class KAPinField : UITextField { 70 | 71 | public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 72 | super.traitCollectionDidChange(previousTraitCollection) 73 | self.reload() 74 | } 75 | 76 | // Mark: - Public vars 77 | private (set) var properties = KAPinFieldProperties() { 78 | didSet { 79 | self.reload() 80 | } 81 | } 82 | private (set) var appearance = KAPinFieldAppearance() { 83 | didSet { 84 | self.reloadAppearance() 85 | } 86 | } 87 | 88 | public func updateProperties(block : ((inout KAPinFieldProperties) -> ())) { 89 | var properties = self.properties 90 | block(&properties) 91 | self.properties = properties 92 | } 93 | 94 | public func updateAppearence(block : ((inout KAPinFieldAppearance) -> ())) { 95 | var appearance = self.appearance 96 | block(&appearance) 97 | self.appearance = appearance 98 | } 99 | 100 | // Mark: - Overriden vars 101 | public override var text : String? { 102 | get { return invisibleText } 103 | set { 104 | self.invisibleField.text = newValue 105 | } 106 | } 107 | 108 | public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { 109 | return action == #selector(paste(_:)) // Only allow pasting 110 | } 111 | 112 | // Mark: - Private vars 113 | 114 | private var isRightToLeft : Bool { 115 | return UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft 116 | } 117 | 118 | // Uses an invisible UITextField to handle text 119 | // this is necessary for iOS12 .oneTimePassword feature 120 | // Remove textField.inputView = UIView() to fix issue with keyboard 121 | private var invisibleField: UITextField = { 122 | let textField = UITextField() 123 | return textField 124 | }() 125 | private var invisibleText : String { 126 | get { 127 | return invisibleField.text ?? "" 128 | } 129 | set { 130 | self.reloadAppearance() 131 | } 132 | } 133 | 134 | private var attributes: [NSAttributedString.Key : Any] = [:] 135 | private var backViews: [UIView] = [UIView]() 136 | private var isAnimating: Bool = false 137 | private var lastEntry: String = "" 138 | private var timer : Timer? 139 | private var currentFocusRange : NSRange? 140 | private var previousCode : String? 141 | private var isDynamicLength = false 142 | private var toolbar : UIToolbar? 143 | 144 | // Mark: - UIKeyInput 145 | public override func insertText(_ text: String) { 146 | self.invisibleField.insertText(text) 147 | } 148 | 149 | public override func deleteBackward() { 150 | self.invisibleField.deleteBackward() 151 | } 152 | 153 | public override var hasText: Bool { 154 | return self.invisibleField.hasText 155 | } 156 | 157 | // Mark: - Lifecycle 158 | 159 | public override var keyboardAppearance: UIKeyboardAppearance { 160 | get { return self.invisibleField.keyboardAppearance } 161 | set { self.invisibleField.keyboardAppearance = newValue} 162 | } 163 | 164 | public override var keyboardType: UIKeyboardType { 165 | get { return self.invisibleField.keyboardType } 166 | set { self.invisibleField.keyboardType = newValue} 167 | } 168 | 169 | public override func reloadInputViews() { 170 | invisibleField.reloadInputViews() 171 | } 172 | 173 | override public func awakeFromNib() { 174 | super.awakeFromNib() 175 | self.reload() 176 | } 177 | 178 | override public func layoutSubviews() { 179 | super.layoutSubviews() 180 | self.bringSubviewToFront(self.invisibleField) 181 | self.invisibleField.frame = self.bounds 182 | 183 | guard !self.isAnimating, !self.isDynamicLength else { 184 | return 185 | } 186 | 187 | // back views 188 | var myText = "" 189 | for _ in 0.. Bool { 237 | return self.invisibleField.becomeFirstResponder() 238 | } 239 | 240 | public func animateFailure(_ completion : (() -> Void)? = nil) { 241 | 242 | guard !self.isAnimating else { 243 | return 244 | } 245 | 246 | isAnimating = true 247 | 248 | CATransaction.begin() 249 | CATransaction.setCompletionBlock({ 250 | self.isAnimating = false 251 | completion?() 252 | self.reloadAppearance() 253 | }) 254 | 255 | let animation = CAKeyframeAnimation(keyPath: "transform.translation.x") 256 | animation.timingFunction = CAMediaTimingFunction.init(name: .linear) 257 | animation.duration = 0.6 258 | animation.values = [-14.0, 14.0, -14.0, 14.0, -8.0, 8.0, -4.0, 4.0, 0.0 ] 259 | layer.add(animation, forKey: "shake") 260 | 261 | CATransaction.commit() 262 | } 263 | 264 | public func animateSuccess(with text: String, completion : (() -> Void)? = nil) { 265 | 266 | guard !self.isAnimating else { 267 | return 268 | } 269 | 270 | self.isAnimating = true 271 | 272 | UIView.animate(withDuration: 0.2, animations: { 273 | 274 | for v in self.backViews { 275 | v.alpha = 0 276 | } 277 | 278 | self.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) 279 | self.alpha = 0 280 | }) { _ in 281 | self.attributedText = NSAttributedString(string: text, attributes: self.attributes) 282 | UIView.animate(withDuration: 0.2, animations: { 283 | self.transform = CGAffineTransform.identity 284 | self.alpha = 1.0 285 | 286 | }) { _ in 287 | 288 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { 289 | self.isAnimating = false 290 | completion?() 291 | } 292 | 293 | } 294 | } 295 | } 296 | 297 | // Mark: - Private function 298 | 299 | @objc func cancelNumberPad() { 300 | self.endEditing(true) 301 | } 302 | 303 | @objc func doneWithNumberPad() { 304 | self.properties.delegate?.pinField(self, didFinishWith: self.invisibleText) 305 | } 306 | 307 | private func reload() { 308 | 309 | // Dynamic length flag 310 | isDynamicLength = (self.properties.numberOfCharacters == 0) 311 | 312 | // Only setup if view showing 313 | guard self.superview != nil else { 314 | return 315 | } 316 | 317 | self.endEditing(true) 318 | if isDynamicLength { 319 | if self.inputAccessoryView == nil { 320 | 321 | let frame = CGRect(x: 0, 322 | y: 0, 323 | width: UIScreen.main.bounds.width, 324 | height: 50) 325 | let numberToolbar = UIToolbar(frame:frame) 326 | numberToolbar.barStyle = .default 327 | numberToolbar.items = [ 328 | UIBarButtonItem.init(barButtonSystemItem: .cancel, target: self, action: #selector(cancelNumberPad)), 329 | UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), 330 | UIBarButtonItem.init(barButtonSystemItem: .done, target: self, action: #selector(doneWithNumberPad)) 331 | ] 332 | numberToolbar.sizeToFit() 333 | self.inputAccessoryView = numberToolbar 334 | } 335 | } else { 336 | 337 | self.inputAccessoryView = nil 338 | } 339 | 340 | // Debugging --------------- 341 | // Change alpha for easy debug 342 | let alpha: CGFloat = 0.0 343 | self.invisibleField.backgroundColor = UIColor.white.withAlphaComponent(alpha * 0.8) 344 | self.invisibleField.tintColor = UIColor.black.withAlphaComponent(alpha) 345 | self.invisibleField.textColor = UIColor.black.withAlphaComponent(alpha) 346 | // -------------------------- 347 | 348 | // Prepare `invisibleField` 349 | self.invisibleField.textAlignment = .center 350 | self.invisibleField.autocapitalizationType = .none 351 | self.invisibleField.autocorrectionType = .no 352 | self.invisibleField.spellCheckingType = .no 353 | 354 | self.invisibleField.keyboardType = self.properties.keyboardType 355 | 356 | if #available(iOS 12.0, *) { 357 | // Show possible prediction on iOS >= 12 358 | self.invisibleField.textContentType = .oneTimeCode 359 | self.invisibleField.autocorrectionType = .yes 360 | } 361 | 362 | self.addSubview(self.invisibleField) 363 | self.invisibleField.addTarget(self, action: #selector(reloadAppearance), for: .allEditingEvents) 364 | 365 | // Prepare visible field 366 | self.tintColor = .clear // Hide cursor 367 | self.invisibleField.tintColor = .clear // Hide cursor 368 | self.contentVerticalAlignment = .center 369 | 370 | // Set back views 371 | for v in self.backViews { 372 | v.removeFromSuperview() 373 | } 374 | self.backViews.removeAll(keepingCapacity: false) 375 | for _ in 0.. Bool in 536 | return result && self.properties.validCharacters.contains(char) 537 | } 538 | if text.count <= self.properties.numberOfCharacters { 539 | self.properties.delegate?.pinField(self, didChangeTo: text, isValid: isValid) 540 | } 541 | 542 | lastEntry = text 543 | } 544 | 545 | text = String(text.lazy.filter(self.properties.validCharacters.contains)) 546 | 547 | if !self.isDynamicLength { 548 | text = String(text.prefix(self.properties.numberOfCharacters)) 549 | } 550 | 551 | self.invisibleField.text = text 552 | } 553 | 554 | // Always position cursor on last valid character 555 | private func updateCursorPosition() { 556 | self.currentFocusRange = nil 557 | let offset = min(self.invisibleText.count, self.properties.numberOfCharacters) 558 | // Only works with a small delay 559 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { 560 | if let position = self.invisibleField.position(from: self.invisibleField.beginningOfDocument, offset: offset) { 561 | 562 | let textRange = self.textRange(from: position, to: position) 563 | self.invisibleField.selectedTextRange = textRange 564 | 565 | // Compute the currently focused element 566 | if let attString = self.attributedText?.mutableCopy() as? NSMutableAttributedString, 567 | var range = self.invisibleField.selectedRange, 568 | range.location >= -1 && range.location < self.properties.numberOfCharacters { 569 | 570 | // Compute range of focused text 571 | if self.isRightToLeft { 572 | range.location = self.properties.numberOfCharacters-range.location-1 573 | } 574 | range.length = 1 575 | 576 | // Make sure it's a token that is focused 577 | let string = attString.string 578 | let startIndex = string.index(string.startIndex, offsetBy: range.location) 579 | let endIndex = string.index(startIndex, offsetBy: 1) 580 | let sub = string[startIndex.. UIFont { 654 | switch self { 655 | case .courier(let size) : 656 | return UIFont(name: "Courier", size: size)! 657 | case .courierBold(let size) : 658 | return UIFont(name: "Courier-Bold", size: size)! 659 | case .courierBoldOblique(let size) : 660 | return UIFont(name: "Courier-BoldOblique", size: size)! 661 | case .courierOblique(let size) : 662 | return UIFont(name: "Courier-Oblique", size: size)! 663 | case .courierNewBoldItalic(let size) : 664 | return UIFont(name: "CourierNewPS-BoldItalicMT", size: size)! 665 | case .courierNewBold(let size) : 666 | return UIFont(name: "CourierNewPS-BoldMT", size: size)! 667 | case .courierNewItalic(let size) : 668 | return UIFont(name: "CourierNewPS-ItalicMT", size: size)! 669 | case .courierNew(let size) : 670 | return UIFont(name: "CourierNewPSMT", size: size)! 671 | case .menloBold(let size) : 672 | return UIFont(name: "Menlo-Bold", size: size)! 673 | case .menloBoldItalic(let size) : 674 | return UIFont(name: "Menlo-BoldItalic", size: size)! 675 | case .menloItalic(let size) : 676 | return UIFont(name: "Menlo-Italic", size: size)! 677 | case .menlo(let size) : 678 | return UIFont(name: "Menlo-Regular", size: size)! 679 | } 680 | } 681 | } 682 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alexis Creuzot 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.4 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "KAPinField", 7 | platforms: [ 8 | .iOS(.v9), 9 | ], 10 | products: [ 11 | .library( 12 | name: "KAPinField", 13 | targets: ["KAPinField"] 14 | ), 15 | ], 16 | dependencies: [], 17 | targets: [ 18 | .target( 19 | name: "KAPinField", 20 | path: "KAPinField", 21 | exclude: [ 22 | "Example", 23 | "Sources/Info.plist", 24 | "Sources/KAPinField.h", 25 | ], 26 | sources: [ 27 | "Sources", 28 | ], 29 | linkerSettings: [ 30 | .linkedFramework("UIKit"), 31 | ] 32 | ), 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Platform](https://img.shields.io/cocoapods/p/KAPinField.svg?style=flat)](https://alamofire.github.io/KAPinField) 2 | [![Language](https://img.shields.io/badge/swift-5.0-blue.svg)](http://swift.org) 3 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/KAPinField.svg)](https://img.shields.io/cocoapods/v/KAPinField.svg) 4 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-blue.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | [![Build Status](https://travis-ci.org/kirualex/KAPinField.svg?branch=master)](https://travis-ci.org/kirualex/KAPinField) 6 | [![Pod License](http://img.shields.io/cocoapods/l/SDWebImage.svg?style=flat)](https://raw.githubusercontent.com/kirualex/SwiftyGif/master/LICENSE) 7 | 8 | # KAPinField 9 | ### Lightweight pin code field library for iOS, written in Swift 10 | 11 | **Supports one time password autofill out of the box !** 12 | 13 | 14 | 15 | 18 | 19 | 20 | 23 | 24 |
16 | 17 |
21 | Example 22 |
25 | 26 | ## Install 27 | With Cocoapods 28 | `pod 'KAPinField'` 29 | 30 | ## Usage 31 | 32 | ```swift 33 | import KAPinField 34 | 35 | class MyController : UIVIewController { 36 | ... 37 | } 38 | ``` 39 | 40 | ### Storyboard 41 | You can add an UITextField directly in your Storyboard scene and declare it as `KAPinField`. It will automagically become a pin field. You can then customize it from the inspector view to suit your needs. 42 | 43 | ### Delegation 44 | Don't forget to set the delegate likeso : 45 | ```swift 46 | 47 | @IBOutlet var pinField: KAPinField! 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | properties.delegate = self 52 | ... 53 | } 54 | ``` 55 | 56 | One simple method will be called on your delegate 57 | ```swift 58 | extension MyController : KAPinFieldDelegate { 59 | func pinField(_ field: KAPinField, didFinishWith code: String) { 60 | print("didFinishWith : \(code)") 61 | } 62 | } 63 | ``` 64 | 65 | ### Properties 66 | All the logic properties are available in the `KAPinFieldProperties` struct named `properties`. 67 | 68 | **Token can't be a whitespace due to Apple handling of trailing spaces. You can achieve the same effect using any token with `tokenColor` and `tokenFocusColor` set to `.clear`** 69 | 70 | ##### Logic 71 | ```swift 72 | pinField.updateProperties { properties in 73 | properties.token = "-" // Default to "•", can't be a whitespace ! 74 | properties.numberOfCharacters = 5 // Default to 4 75 | properties.validCharacters = "0123456789+#?" // Default to only numbers, "0123456789" 76 | properties.text = "123" // You can set part or all of the text 77 | properties.animateFocus = true // Animate the currently focused token 78 | properties.isSecure = false // Secure pinField will hide actual input 79 | properties.secureToken = "*" // Token used to hide actual character input when using isSecure = true 80 | properties.isUppercased = false // You can set this to convert input to uppercased. 81 | } 82 | ``` 83 | 84 | ##### Styling 85 | All the styling can be done via the `KAPinFieldAppearance` struct named `appearance`. 86 | 87 | ```swift 88 | pinField.updateAppearence { appearance in 89 | appearance.font = .menloBold(40) // Default to appearance.MonospacedFont.menlo(40) 90 | appearance.kerning = 20 // Space between characters, default to 16 91 | appearance.textColor = UIColor.white.withAlphaComponent(1.0) // Default to nib color or black if initialized programmatically. 92 | appearance.tokenColor = UIColor.black.withAlphaComponent(0.3) // token color, default to text color 93 | appearance.tokenFocusColor = UIColor.black.withAlphaComponent(0.3) // token focus color, default to token color 94 | appearance.backOffset = 8 // Backviews spacing between each other 95 | appearance.backColor = UIColor.clear 96 | appearance.backBorderWidth = 1 97 | appearance.backBorderColor = UIColor.white.withAlphaComponent(0.2) 98 | appearance.backCornerRadius = 4 99 | appearance.backFocusColor = UIColor.clear 100 | appearance.backBorderFocusColor = UIColor.white.withAlphaComponent(0.8) 101 | appearance.backActiveColor = UIColor.clear 102 | appearance.backBorderActiveColor = UIColor.white 103 | appearance.keyboardType = UIKeyboardType.numberPad // Specify keyboard type 104 | } 105 | ``` 106 | 107 | ### Font 108 | A [monospaced font](https://en.wikipedia.org/wiki/Monospaced_font) is highly recommended in order to avoid horizontal offsetting during typing. For this purpose, a handy helper is available to allow you to access native iOS monospaced fonts. 109 | To use it, just set `appearance.font` with a enum value from `appearance.MonospacedFont`. 110 | You can of course still use your own font by setting the default `font` property on KAPinField. 111 | 112 | ### Animation 113 | `KAPinField` also provide some eye-candy for failure and success. 114 | 115 | ##### Success 116 | ```swift 117 | pinfield.animateSuccess(with: "👍") { 118 | print("Success") 119 | } 120 | ``` 121 | 122 | ##### Failure 123 | ```swift 124 | pinfield.animateFailure() { 125 | print("Failure") 126 | } 127 | ``` 128 | -------------------------------------------------------------------------------- /preview1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexiscreuzot/KAPinField/5abe6d3898d607a7009255449e09998c1af86bf1/preview1.gif -------------------------------------------------------------------------------- /preview2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexiscreuzot/KAPinField/5abe6d3898d607a7009255449e09998c1af86bf1/preview2.gif --------------------------------------------------------------------------------