├── .github └── FUNDING.yml ├── LICENSE ├── Package.swift ├── PassCodeInputDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── devraj.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── PassCodeInputDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Info.plist ├── ModalRootView.swift ├── PassCodeInput │ ├── PassCodeInputCell.swift │ ├── PassCodeInputField.swift │ └── PassCodeInputModel.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── SecondView.swift └── en.lproj │ └── Localizable.strings ├── PassCodeInputDemoTests ├── Info.plist └── PassCodeInputDemoTests.swift ├── PassCodeInputDemoUITests ├── Info.plist └── PassCodeInputDemoUITests.swift └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [devraj] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2020 Devraj Mukherjee 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Package.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 7/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import PackageDescription 10 | -------------------------------------------------------------------------------- /PassCodeInputDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 53; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4D99807A2468334A00D953AE /* SecondView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D9980792468334A00D953AE /* SecondView.swift */; }; 11 | 4D99807C246A33C300D953AE /* ModalRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D99807B246A33C300D953AE /* ModalRootView.swift */; }; 12 | 4DE1ED01245BDAD900A5E4A9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED00245BDAD900A5E4A9 /* AppDelegate.swift */; }; 13 | 4DE1ED03245BDAD900A5E4A9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED02245BDAD900A5E4A9 /* SceneDelegate.swift */; }; 14 | 4DE1ED05245BDAD900A5E4A9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED04245BDAD900A5E4A9 /* ContentView.swift */; }; 15 | 4DE1ED07245BDAD900A5E4A9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4DE1ED06245BDAD900A5E4A9 /* Assets.xcassets */; }; 16 | 4DE1ED0A245BDAD900A5E4A9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4DE1ED09245BDAD900A5E4A9 /* Preview Assets.xcassets */; }; 17 | 4DE1ED0D245BDAD900A5E4A9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4DE1ED0B245BDAD900A5E4A9 /* LaunchScreen.storyboard */; }; 18 | 4DE1ED18245BDAD900A5E4A9 /* PassCodeInputDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED17245BDAD900A5E4A9 /* PassCodeInputDemoTests.swift */; }; 19 | 4DE1ED23245BDAD900A5E4A9 /* PassCodeInputDemoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED22245BDAD900A5E4A9 /* PassCodeInputDemoUITests.swift */; }; 20 | 4DE1ED31245F9BE200A5E4A9 /* PassCodeInputModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED30245F9BE200A5E4A9 /* PassCodeInputModel.swift */; }; 21 | 4DE1ED35245F9F5F00A5E4A9 /* PassCodeInputField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED34245F9F5F00A5E4A9 /* PassCodeInputField.swift */; }; 22 | 4DE1ED38245FBBAD00A5E4A9 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4DE1ED3A245FBBAD00A5E4A9 /* Localizable.strings */; }; 23 | 4DE1ED3C245FEE3400A5E4A9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 4DE1ED3B245FEE3400A5E4A9 /* README.md */; }; 24 | 4DE1ED412461850B00A5E4A9 /* PassCodeInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DE1ED3F2460440900A5E4A9 /* PassCodeInputCell.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXContainerItemProxy section */ 28 | 4DE1ED14245BDAD900A5E4A9 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 4DE1ECF5245BDAD900A5E4A9 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 4DE1ECFC245BDAD900A5E4A9; 33 | remoteInfo = PassCodeInputDemo; 34 | }; 35 | 4DE1ED1F245BDAD900A5E4A9 /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 4DE1ECF5245BDAD900A5E4A9 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 4DE1ECFC245BDAD900A5E4A9; 40 | remoteInfo = PassCodeInputDemo; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 4D9936422463D8BF00F006CC /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 46 | 4D9980792468334A00D953AE /* SecondView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondView.swift; sourceTree = ""; }; 47 | 4D99807B246A33C300D953AE /* ModalRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalRootView.swift; sourceTree = ""; }; 48 | 4DE1ECFD245BDAD900A5E4A9 /* PassCodeInputDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PassCodeInputDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 4DE1ED00245BDAD900A5E4A9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 4DE1ED02245BDAD900A5E4A9 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51 | 4DE1ED04245BDAD900A5E4A9 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 52 | 4DE1ED06245BDAD900A5E4A9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | 4DE1ED09245BDAD900A5E4A9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 54 | 4DE1ED0C245BDAD900A5E4A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 4DE1ED0E245BDAD900A5E4A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 4DE1ED13245BDAD900A5E4A9 /* PassCodeInputDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassCodeInputDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 4DE1ED17245BDAD900A5E4A9 /* PassCodeInputDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassCodeInputDemoTests.swift; sourceTree = ""; }; 58 | 4DE1ED19245BDAD900A5E4A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 4DE1ED1E245BDAD900A5E4A9 /* PassCodeInputDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PassCodeInputDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 60 | 4DE1ED22245BDAD900A5E4A9 /* PassCodeInputDemoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassCodeInputDemoUITests.swift; sourceTree = ""; }; 61 | 4DE1ED24245BDAD900A5E4A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 4DE1ED30245F9BE200A5E4A9 /* PassCodeInputModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassCodeInputModel.swift; sourceTree = ""; }; 63 | 4DE1ED34245F9F5F00A5E4A9 /* PassCodeInputField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassCodeInputField.swift; sourceTree = ""; }; 64 | 4DE1ED39245FBBAD00A5E4A9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 65 | 4DE1ED3B245FEE3400A5E4A9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 66 | 4DE1ED3F2460440900A5E4A9 /* PassCodeInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PassCodeInputCell.swift; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 4DE1ECFA245BDAD900A5E4A9 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | 4DE1ED10245BDAD900A5E4A9 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | 4DE1ED1B245BDAD900A5E4A9 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 4D9936392462EFF600F006CC /* PassCodeInput */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 4DE1ED30245F9BE200A5E4A9 /* PassCodeInputModel.swift */, 98 | 4DE1ED34245F9F5F00A5E4A9 /* PassCodeInputField.swift */, 99 | 4DE1ED3F2460440900A5E4A9 /* PassCodeInputCell.swift */, 100 | ); 101 | path = PassCodeInput; 102 | sourceTree = ""; 103 | }; 104 | 4DE1ECF4245BDAD900A5E4A9 = { 105 | isa = PBXGroup; 106 | children = ( 107 | 4D9936422463D8BF00F006CC /* Package.swift */, 108 | 4DE1ED3B245FEE3400A5E4A9 /* README.md */, 109 | 4DE1ECFF245BDAD900A5E4A9 /* PassCodeInputDemo */, 110 | 4DE1ED16245BDAD900A5E4A9 /* PassCodeInputDemoTests */, 111 | 4DE1ED21245BDAD900A5E4A9 /* PassCodeInputDemoUITests */, 112 | 4DE1ECFE245BDAD900A5E4A9 /* Products */, 113 | ); 114 | sourceTree = ""; 115 | }; 116 | 4DE1ECFE245BDAD900A5E4A9 /* Products */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 4DE1ECFD245BDAD900A5E4A9 /* PassCodeInputDemo.app */, 120 | 4DE1ED13245BDAD900A5E4A9 /* PassCodeInputDemoTests.xctest */, 121 | 4DE1ED1E245BDAD900A5E4A9 /* PassCodeInputDemoUITests.xctest */, 122 | ); 123 | name = Products; 124 | sourceTree = ""; 125 | }; 126 | 4DE1ECFF245BDAD900A5E4A9 /* PassCodeInputDemo */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 4D9936392462EFF600F006CC /* PassCodeInput */, 130 | 4DE1ED00245BDAD900A5E4A9 /* AppDelegate.swift */, 131 | 4DE1ED02245BDAD900A5E4A9 /* SceneDelegate.swift */, 132 | 4DE1ED04245BDAD900A5E4A9 /* ContentView.swift */, 133 | 4D9980792468334A00D953AE /* SecondView.swift */, 134 | 4D99807B246A33C300D953AE /* ModalRootView.swift */, 135 | 4DE1ED06245BDAD900A5E4A9 /* Assets.xcassets */, 136 | 4DE1ED0B245BDAD900A5E4A9 /* LaunchScreen.storyboard */, 137 | 4DE1ED0E245BDAD900A5E4A9 /* Info.plist */, 138 | 4DE1ED08245BDAD900A5E4A9 /* Preview Content */, 139 | 4DE1ED3A245FBBAD00A5E4A9 /* Localizable.strings */, 140 | ); 141 | path = PassCodeInputDemo; 142 | sourceTree = ""; 143 | }; 144 | 4DE1ED08245BDAD900A5E4A9 /* Preview Content */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 4DE1ED09245BDAD900A5E4A9 /* Preview Assets.xcassets */, 148 | ); 149 | path = "Preview Content"; 150 | sourceTree = ""; 151 | }; 152 | 4DE1ED16245BDAD900A5E4A9 /* PassCodeInputDemoTests */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 4DE1ED17245BDAD900A5E4A9 /* PassCodeInputDemoTests.swift */, 156 | 4DE1ED19245BDAD900A5E4A9 /* Info.plist */, 157 | ); 158 | path = PassCodeInputDemoTests; 159 | sourceTree = ""; 160 | }; 161 | 4DE1ED21245BDAD900A5E4A9 /* PassCodeInputDemoUITests */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 4DE1ED22245BDAD900A5E4A9 /* PassCodeInputDemoUITests.swift */, 165 | 4DE1ED24245BDAD900A5E4A9 /* Info.plist */, 166 | ); 167 | path = PassCodeInputDemoUITests; 168 | sourceTree = ""; 169 | }; 170 | /* End PBXGroup section */ 171 | 172 | /* Begin PBXNativeTarget section */ 173 | 4DE1ECFC245BDAD900A5E4A9 /* PassCodeInputDemo */ = { 174 | isa = PBXNativeTarget; 175 | buildConfigurationList = 4DE1ED27245BDAD900A5E4A9 /* Build configuration list for PBXNativeTarget "PassCodeInputDemo" */; 176 | buildPhases = ( 177 | 4DE1ECF9245BDAD900A5E4A9 /* Sources */, 178 | 4DE1ECFA245BDAD900A5E4A9 /* Frameworks */, 179 | 4DE1ECFB245BDAD900A5E4A9 /* Resources */, 180 | ); 181 | buildRules = ( 182 | ); 183 | dependencies = ( 184 | ); 185 | name = PassCodeInputDemo; 186 | productName = PassCodeInputDemo; 187 | productReference = 4DE1ECFD245BDAD900A5E4A9 /* PassCodeInputDemo.app */; 188 | productType = "com.apple.product-type.application"; 189 | }; 190 | 4DE1ED12245BDAD900A5E4A9 /* PassCodeInputDemoTests */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 4DE1ED2A245BDAD900A5E4A9 /* Build configuration list for PBXNativeTarget "PassCodeInputDemoTests" */; 193 | buildPhases = ( 194 | 4DE1ED0F245BDAD900A5E4A9 /* Sources */, 195 | 4DE1ED10245BDAD900A5E4A9 /* Frameworks */, 196 | 4DE1ED11245BDAD900A5E4A9 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | 4DE1ED15245BDAD900A5E4A9 /* PBXTargetDependency */, 202 | ); 203 | name = PassCodeInputDemoTests; 204 | productName = PassCodeInputDemoTests; 205 | productReference = 4DE1ED13245BDAD900A5E4A9 /* PassCodeInputDemoTests.xctest */; 206 | productType = "com.apple.product-type.bundle.unit-test"; 207 | }; 208 | 4DE1ED1D245BDAD900A5E4A9 /* PassCodeInputDemoUITests */ = { 209 | isa = PBXNativeTarget; 210 | buildConfigurationList = 4DE1ED2D245BDAD900A5E4A9 /* Build configuration list for PBXNativeTarget "PassCodeInputDemoUITests" */; 211 | buildPhases = ( 212 | 4DE1ED1A245BDAD900A5E4A9 /* Sources */, 213 | 4DE1ED1B245BDAD900A5E4A9 /* Frameworks */, 214 | 4DE1ED1C245BDAD900A5E4A9 /* Resources */, 215 | ); 216 | buildRules = ( 217 | ); 218 | dependencies = ( 219 | 4DE1ED20245BDAD900A5E4A9 /* PBXTargetDependency */, 220 | ); 221 | name = PassCodeInputDemoUITests; 222 | productName = PassCodeInputDemoUITests; 223 | productReference = 4DE1ED1E245BDAD900A5E4A9 /* PassCodeInputDemoUITests.xctest */; 224 | productType = "com.apple.product-type.bundle.ui-testing"; 225 | }; 226 | /* End PBXNativeTarget section */ 227 | 228 | /* Begin PBXProject section */ 229 | 4DE1ECF5245BDAD900A5E4A9 /* Project object */ = { 230 | isa = PBXProject; 231 | attributes = { 232 | LastSwiftUpdateCheck = 1140; 233 | LastUpgradeCheck = 1140; 234 | ORGANIZATIONNAME = "Anomaly Software"; 235 | TargetAttributes = { 236 | 4DE1ECFC245BDAD900A5E4A9 = { 237 | CreatedOnToolsVersion = 11.4.1; 238 | }; 239 | 4DE1ED12245BDAD900A5E4A9 = { 240 | CreatedOnToolsVersion = 11.4.1; 241 | TestTargetID = 4DE1ECFC245BDAD900A5E4A9; 242 | }; 243 | 4DE1ED1D245BDAD900A5E4A9 = { 244 | CreatedOnToolsVersion = 11.4.1; 245 | TestTargetID = 4DE1ECFC245BDAD900A5E4A9; 246 | }; 247 | }; 248 | }; 249 | buildConfigurationList = 4DE1ECF8245BDAD900A5E4A9 /* Build configuration list for PBXProject "PassCodeInputDemo" */; 250 | compatibilityVersion = "Xcode 11.4"; 251 | developmentRegion = en; 252 | hasScannedForEncodings = 0; 253 | knownRegions = ( 254 | en, 255 | Base, 256 | ); 257 | mainGroup = 4DE1ECF4245BDAD900A5E4A9; 258 | productRefGroup = 4DE1ECFE245BDAD900A5E4A9 /* Products */; 259 | projectDirPath = ""; 260 | projectRoot = ""; 261 | targets = ( 262 | 4DE1ECFC245BDAD900A5E4A9 /* PassCodeInputDemo */, 263 | 4DE1ED12245BDAD900A5E4A9 /* PassCodeInputDemoTests */, 264 | 4DE1ED1D245BDAD900A5E4A9 /* PassCodeInputDemoUITests */, 265 | ); 266 | }; 267 | /* End PBXProject section */ 268 | 269 | /* Begin PBXResourcesBuildPhase section */ 270 | 4DE1ECFB245BDAD900A5E4A9 /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 4DE1ED0D245BDAD900A5E4A9 /* LaunchScreen.storyboard in Resources */, 275 | 4DE1ED38245FBBAD00A5E4A9 /* Localizable.strings in Resources */, 276 | 4DE1ED3C245FEE3400A5E4A9 /* README.md in Resources */, 277 | 4DE1ED0A245BDAD900A5E4A9 /* Preview Assets.xcassets in Resources */, 278 | 4DE1ED07245BDAD900A5E4A9 /* Assets.xcassets in Resources */, 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | 4DE1ED11245BDAD900A5E4A9 /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | 4DE1ED1C245BDAD900A5E4A9 /* Resources */ = { 290 | isa = PBXResourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXResourcesBuildPhase section */ 297 | 298 | /* Begin PBXSourcesBuildPhase section */ 299 | 4DE1ECF9245BDAD900A5E4A9 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 4D99807A2468334A00D953AE /* SecondView.swift in Sources */, 304 | 4DE1ED01245BDAD900A5E4A9 /* AppDelegate.swift in Sources */, 305 | 4D99807C246A33C300D953AE /* ModalRootView.swift in Sources */, 306 | 4DE1ED03245BDAD900A5E4A9 /* SceneDelegate.swift in Sources */, 307 | 4DE1ED31245F9BE200A5E4A9 /* PassCodeInputModel.swift in Sources */, 308 | 4DE1ED05245BDAD900A5E4A9 /* ContentView.swift in Sources */, 309 | 4DE1ED412461850B00A5E4A9 /* PassCodeInputCell.swift in Sources */, 310 | 4DE1ED35245F9F5F00A5E4A9 /* PassCodeInputField.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | 4DE1ED0F245BDAD900A5E4A9 /* Sources */ = { 315 | isa = PBXSourcesBuildPhase; 316 | buildActionMask = 2147483647; 317 | files = ( 318 | 4DE1ED18245BDAD900A5E4A9 /* PassCodeInputDemoTests.swift in Sources */, 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | }; 322 | 4DE1ED1A245BDAD900A5E4A9 /* Sources */ = { 323 | isa = PBXSourcesBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | 4DE1ED23245BDAD900A5E4A9 /* PassCodeInputDemoUITests.swift in Sources */, 327 | ); 328 | runOnlyForDeploymentPostprocessing = 0; 329 | }; 330 | /* End PBXSourcesBuildPhase section */ 331 | 332 | /* Begin PBXTargetDependency section */ 333 | 4DE1ED15245BDAD900A5E4A9 /* PBXTargetDependency */ = { 334 | isa = PBXTargetDependency; 335 | target = 4DE1ECFC245BDAD900A5E4A9 /* PassCodeInputDemo */; 336 | targetProxy = 4DE1ED14245BDAD900A5E4A9 /* PBXContainerItemProxy */; 337 | }; 338 | 4DE1ED20245BDAD900A5E4A9 /* PBXTargetDependency */ = { 339 | isa = PBXTargetDependency; 340 | target = 4DE1ECFC245BDAD900A5E4A9 /* PassCodeInputDemo */; 341 | targetProxy = 4DE1ED1F245BDAD900A5E4A9 /* PBXContainerItemProxy */; 342 | }; 343 | /* End PBXTargetDependency section */ 344 | 345 | /* Begin PBXVariantGroup section */ 346 | 4DE1ED0B245BDAD900A5E4A9 /* LaunchScreen.storyboard */ = { 347 | isa = PBXVariantGroup; 348 | children = ( 349 | 4DE1ED0C245BDAD900A5E4A9 /* Base */, 350 | ); 351 | name = LaunchScreen.storyboard; 352 | sourceTree = ""; 353 | }; 354 | 4DE1ED3A245FBBAD00A5E4A9 /* Localizable.strings */ = { 355 | isa = PBXVariantGroup; 356 | children = ( 357 | 4DE1ED39245FBBAD00A5E4A9 /* en */, 358 | ); 359 | name = Localizable.strings; 360 | sourceTree = ""; 361 | }; 362 | /* End PBXVariantGroup section */ 363 | 364 | /* Begin XCBuildConfiguration section */ 365 | 4DE1ED25245BDAD900A5E4A9 /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_ANALYZER_NONNULL = YES; 370 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 371 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 372 | CLANG_CXX_LIBRARY = "libc++"; 373 | CLANG_ENABLE_MODULES = YES; 374 | CLANG_ENABLE_OBJC_ARC = YES; 375 | CLANG_ENABLE_OBJC_WEAK = YES; 376 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 377 | CLANG_WARN_BOOL_CONVERSION = YES; 378 | CLANG_WARN_COMMA = YES; 379 | CLANG_WARN_CONSTANT_CONVERSION = YES; 380 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INFINITE_RECURSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 389 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 392 | CLANG_WARN_STRICT_PROTOTYPES = YES; 393 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 394 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 395 | CLANG_WARN_UNREACHABLE_CODE = YES; 396 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 397 | COPY_PHASE_STRIP = NO; 398 | DEBUG_INFORMATION_FORMAT = dwarf; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | ENABLE_TESTABILITY = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu11; 402 | GCC_DYNAMIC_NO_PIC = NO; 403 | GCC_NO_COMMON_BLOCKS = YES; 404 | GCC_OPTIMIZATION_LEVEL = 0; 405 | GCC_PREPROCESSOR_DEFINITIONS = ( 406 | "DEBUG=1", 407 | "$(inherited)", 408 | ); 409 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 410 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 411 | GCC_WARN_UNDECLARED_SELECTOR = YES; 412 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 413 | GCC_WARN_UNUSED_FUNCTION = YES; 414 | GCC_WARN_UNUSED_VARIABLE = YES; 415 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 416 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 417 | MTL_FAST_MATH = YES; 418 | ONLY_ACTIVE_ARCH = YES; 419 | SDKROOT = iphoneos; 420 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | }; 423 | name = Debug; 424 | }; 425 | 4DE1ED26245BDAD900A5E4A9 /* Release */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | CLANG_ANALYZER_NONNULL = YES; 430 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 432 | CLANG_CXX_LIBRARY = "libc++"; 433 | CLANG_ENABLE_MODULES = YES; 434 | CLANG_ENABLE_OBJC_ARC = YES; 435 | CLANG_ENABLE_OBJC_WEAK = YES; 436 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 437 | CLANG_WARN_BOOL_CONVERSION = YES; 438 | CLANG_WARN_COMMA = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 443 | CLANG_WARN_EMPTY_BODY = YES; 444 | CLANG_WARN_ENUM_CONVERSION = YES; 445 | CLANG_WARN_INFINITE_RECURSION = YES; 446 | CLANG_WARN_INT_CONVERSION = YES; 447 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 449 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 450 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 455 | CLANG_WARN_UNREACHABLE_CODE = YES; 456 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 457 | COPY_PHASE_STRIP = NO; 458 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 459 | ENABLE_NS_ASSERTIONS = NO; 460 | ENABLE_STRICT_OBJC_MSGSEND = YES; 461 | GCC_C_LANGUAGE_STANDARD = gnu11; 462 | GCC_NO_COMMON_BLOCKS = YES; 463 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 464 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 465 | GCC_WARN_UNDECLARED_SELECTOR = YES; 466 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 467 | GCC_WARN_UNUSED_FUNCTION = YES; 468 | GCC_WARN_UNUSED_VARIABLE = YES; 469 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 470 | MTL_ENABLE_DEBUG_INFO = NO; 471 | MTL_FAST_MATH = YES; 472 | SDKROOT = iphoneos; 473 | SWIFT_COMPILATION_MODE = wholemodule; 474 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 475 | VALIDATE_PRODUCT = YES; 476 | }; 477 | name = Release; 478 | }; 479 | 4DE1ED28245BDAD900A5E4A9 /* Debug */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 483 | CODE_SIGN_STYLE = Automatic; 484 | DEVELOPMENT_ASSET_PATHS = "\"PassCodeInputDemo/Preview Content\""; 485 | DEVELOPMENT_TEAM = MVFG49B2CV; 486 | ENABLE_PREVIEWS = YES; 487 | INFOPLIST_FILE = PassCodeInputDemo/Info.plist; 488 | LD_RUNPATH_SEARCH_PATHS = ( 489 | "$(inherited)", 490 | "@executable_path/Frameworks", 491 | ); 492 | PRODUCT_BUNDLE_IDENTIFIER = au.net.anomaly.PassCodeInputDemo; 493 | PRODUCT_NAME = "$(TARGET_NAME)"; 494 | SWIFT_VERSION = 5.0; 495 | TARGETED_DEVICE_FAMILY = "1,2"; 496 | }; 497 | name = Debug; 498 | }; 499 | 4DE1ED29245BDAD900A5E4A9 /* Release */ = { 500 | isa = XCBuildConfiguration; 501 | buildSettings = { 502 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 503 | CODE_SIGN_STYLE = Automatic; 504 | DEVELOPMENT_ASSET_PATHS = "\"PassCodeInputDemo/Preview Content\""; 505 | DEVELOPMENT_TEAM = MVFG49B2CV; 506 | ENABLE_PREVIEWS = YES; 507 | INFOPLIST_FILE = PassCodeInputDemo/Info.plist; 508 | LD_RUNPATH_SEARCH_PATHS = ( 509 | "$(inherited)", 510 | "@executable_path/Frameworks", 511 | ); 512 | PRODUCT_BUNDLE_IDENTIFIER = au.net.anomaly.PassCodeInputDemo; 513 | PRODUCT_NAME = "$(TARGET_NAME)"; 514 | SWIFT_VERSION = 5.0; 515 | TARGETED_DEVICE_FAMILY = "1,2"; 516 | }; 517 | name = Release; 518 | }; 519 | 4DE1ED2B245BDAD900A5E4A9 /* Debug */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 523 | BUNDLE_LOADER = "$(TEST_HOST)"; 524 | CODE_SIGN_STYLE = Automatic; 525 | DEVELOPMENT_TEAM = MVFG49B2CV; 526 | INFOPLIST_FILE = PassCodeInputDemoTests/Info.plist; 527 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 528 | LD_RUNPATH_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "@executable_path/Frameworks", 531 | "@loader_path/Frameworks", 532 | ); 533 | PRODUCT_BUNDLE_IDENTIFIER = au.net.anomaly.PassCodeInputDemoTests; 534 | PRODUCT_NAME = "$(TARGET_NAME)"; 535 | SWIFT_VERSION = 5.0; 536 | TARGETED_DEVICE_FAMILY = "1,2"; 537 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PassCodeInputDemo.app/PassCodeInputDemo"; 538 | }; 539 | name = Debug; 540 | }; 541 | 4DE1ED2C245BDAD900A5E4A9 /* Release */ = { 542 | isa = XCBuildConfiguration; 543 | buildSettings = { 544 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 545 | BUNDLE_LOADER = "$(TEST_HOST)"; 546 | CODE_SIGN_STYLE = Automatic; 547 | DEVELOPMENT_TEAM = MVFG49B2CV; 548 | INFOPLIST_FILE = PassCodeInputDemoTests/Info.plist; 549 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 550 | LD_RUNPATH_SEARCH_PATHS = ( 551 | "$(inherited)", 552 | "@executable_path/Frameworks", 553 | "@loader_path/Frameworks", 554 | ); 555 | PRODUCT_BUNDLE_IDENTIFIER = au.net.anomaly.PassCodeInputDemoTests; 556 | PRODUCT_NAME = "$(TARGET_NAME)"; 557 | SWIFT_VERSION = 5.0; 558 | TARGETED_DEVICE_FAMILY = "1,2"; 559 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PassCodeInputDemo.app/PassCodeInputDemo"; 560 | }; 561 | name = Release; 562 | }; 563 | 4DE1ED2E245BDAD900A5E4A9 /* Debug */ = { 564 | isa = XCBuildConfiguration; 565 | buildSettings = { 566 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 567 | CODE_SIGN_STYLE = Automatic; 568 | DEVELOPMENT_TEAM = MVFG49B2CV; 569 | INFOPLIST_FILE = PassCodeInputDemoUITests/Info.plist; 570 | LD_RUNPATH_SEARCH_PATHS = ( 571 | "$(inherited)", 572 | "@executable_path/Frameworks", 573 | "@loader_path/Frameworks", 574 | ); 575 | PRODUCT_BUNDLE_IDENTIFIER = au.net.anomaly.PassCodeInputDemoUITests; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | SWIFT_VERSION = 5.0; 578 | TARGETED_DEVICE_FAMILY = "1,2"; 579 | TEST_TARGET_NAME = PassCodeInputDemo; 580 | }; 581 | name = Debug; 582 | }; 583 | 4DE1ED2F245BDAD900A5E4A9 /* Release */ = { 584 | isa = XCBuildConfiguration; 585 | buildSettings = { 586 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 587 | CODE_SIGN_STYLE = Automatic; 588 | DEVELOPMENT_TEAM = MVFG49B2CV; 589 | INFOPLIST_FILE = PassCodeInputDemoUITests/Info.plist; 590 | LD_RUNPATH_SEARCH_PATHS = ( 591 | "$(inherited)", 592 | "@executable_path/Frameworks", 593 | "@loader_path/Frameworks", 594 | ); 595 | PRODUCT_BUNDLE_IDENTIFIER = au.net.anomaly.PassCodeInputDemoUITests; 596 | PRODUCT_NAME = "$(TARGET_NAME)"; 597 | SWIFT_VERSION = 5.0; 598 | TARGETED_DEVICE_FAMILY = "1,2"; 599 | TEST_TARGET_NAME = PassCodeInputDemo; 600 | }; 601 | name = Release; 602 | }; 603 | /* End XCBuildConfiguration section */ 604 | 605 | /* Begin XCConfigurationList section */ 606 | 4DE1ECF8245BDAD900A5E4A9 /* Build configuration list for PBXProject "PassCodeInputDemo" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 4DE1ED25245BDAD900A5E4A9 /* Debug */, 610 | 4DE1ED26245BDAD900A5E4A9 /* Release */, 611 | ); 612 | defaultConfigurationIsVisible = 0; 613 | defaultConfigurationName = Release; 614 | }; 615 | 4DE1ED27245BDAD900A5E4A9 /* Build configuration list for PBXNativeTarget "PassCodeInputDemo" */ = { 616 | isa = XCConfigurationList; 617 | buildConfigurations = ( 618 | 4DE1ED28245BDAD900A5E4A9 /* Debug */, 619 | 4DE1ED29245BDAD900A5E4A9 /* Release */, 620 | ); 621 | defaultConfigurationIsVisible = 0; 622 | defaultConfigurationName = Release; 623 | }; 624 | 4DE1ED2A245BDAD900A5E4A9 /* Build configuration list for PBXNativeTarget "PassCodeInputDemoTests" */ = { 625 | isa = XCConfigurationList; 626 | buildConfigurations = ( 627 | 4DE1ED2B245BDAD900A5E4A9 /* Debug */, 628 | 4DE1ED2C245BDAD900A5E4A9 /* Release */, 629 | ); 630 | defaultConfigurationIsVisible = 0; 631 | defaultConfigurationName = Release; 632 | }; 633 | 4DE1ED2D245BDAD900A5E4A9 /* Build configuration list for PBXNativeTarget "PassCodeInputDemoUITests" */ = { 634 | isa = XCConfigurationList; 635 | buildConfigurations = ( 636 | 4DE1ED2E245BDAD900A5E4A9 /* Debug */, 637 | 4DE1ED2F245BDAD900A5E4A9 /* Release */, 638 | ); 639 | defaultConfigurationIsVisible = 0; 640 | defaultConfigurationName = Release; 641 | }; 642 | /* End XCConfigurationList section */ 643 | }; 644 | rootObject = 4DE1ECF5245BDAD900A5E4A9 /* Project object */; 645 | } 646 | -------------------------------------------------------------------------------- /PassCodeInputDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PassCodeInputDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PassCodeInputDemo.xcodeproj/xcuserdata/devraj.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PassCodeInputDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PassCodeInputDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 1/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | // MARK: UISceneSession Lifecycle 20 | 21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 22 | // Called when a new scene session is being created. 23 | // Use this method to select a configuration to create the new scene with. 24 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 25 | } 26 | 27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 28 | // Called when the user discards a scene session. 29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 31 | } 32 | 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /PassCodeInputDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /PassCodeInputDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PassCodeInputDemo/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 | -------------------------------------------------------------------------------- /PassCodeInputDemo/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 1/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | /** 12 | Creates a shake animation, useful in denoting a failed 13 | input attempt. 14 | 15 | With a little help from: 16 | - [How to create an explicit animation](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-an-explicit-animation) 17 | - [SwiftUI: Shake Animation](https://www.objc.io/blog/2019/10/01/swiftui-shake-animation/) 18 | */ 19 | struct Shake: GeometryEffect { 20 | var amount: CGFloat = 10 21 | var shakesPerUnit = 3 22 | var animatableData: CGFloat 23 | 24 | func effectValue(size: CGSize) -> ProjectionTransform { 25 | ProjectionTransform(CGAffineTransform(translationX: 26 | amount * sin(animatableData * .pi * CGFloat(shakesPerUnit)), 27 | y: 0)) 28 | } 29 | } 30 | 31 | struct ContentView: View { 32 | 33 | @ObservedObject var passCodeModel = PassCodeInputModel(passCodeLength: 6) 34 | @ObservedObject var childPassCodeModel = PassCodeInputModel(passCodeLength: 6) 35 | 36 | @State private var attempts: Int = 0 37 | @State private var showModal: Bool = false 38 | 39 | private let modalView: ModalRootView = ModalRootView() 40 | 41 | var body: some View { 42 | 43 | 44 | NavigationView { 45 | Form { 46 | Section { 47 | PassCodeInputField(inputModel: self.passCodeModel) 48 | .modifier(Shake(animatableData: CGFloat(attempts))) 49 | } 50 | Section { 51 | Button(LocalizedStringKey("Prompt_ShakeOff"), action: { 52 | print("Passcode is \(self.passCodeModel.passCodeString)") 53 | 54 | withAnimation(.default) { 55 | self.attempts += 1 56 | } 57 | 58 | }).disabled(!self.passCodeModel.isValid) 59 | } 60 | } 61 | .sheet(isPresented: $showModal) { 62 | return self.modalView 63 | } 64 | .navigationBarTitle(LocalizedStringKey("First_View")) 65 | .navigationBarItems(leading: 66 | Button(action: { 67 | self.showModal.toggle() 68 | }) { 69 | Text(LocalizedStringKey("ModalView")) 70 | }, 71 | trailing: NavigationLink(destination: 72 | SecondView(passCodeModel: self.childPassCodeModel) 73 | .navigationBarTitle(LocalizedStringKey("NestedView"))) 74 | { 75 | Text(LocalizedStringKey("Child View")) 76 | } 77 | ) 78 | } 79 | } 80 | } 81 | 82 | struct ContentView_Previews: PreviewProvider { 83 | static var previews: some View { 84 | ContentView() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /PassCodeInputDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /PassCodeInputDemo/ModalRootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModalRootView.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 12/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ModalRootView: View { 12 | 13 | @ObservedObject var childPassCodeModel = PassCodeInputModel(passCodeLength: 6) 14 | 15 | var body: some View { 16 | NavigationView { 17 | List { 18 | ForEach(0..<5) { index in 19 | Text("List Item") 20 | } 21 | } 22 | .navigationBarTitle(LocalizedStringKey("ModalView")) 23 | .navigationBarItems(trailing: NavigationLink(destination: 24 | SecondView(passCodeModel: self.childPassCodeModel) 25 | .navigationBarTitle(LocalizedStringKey("NestedView"))) 26 | { 27 | Text(LocalizedStringKey("Child View")) 28 | }) 29 | } 30 | } 31 | } 32 | 33 | struct ModalRootView_Previews: PreviewProvider { 34 | static var previews: some View { 35 | ModalRootView() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PassCodeInputDemo/PassCodeInput/PassCodeInputCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassCodeInputTextField.swift.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 4/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | protocol CharacterFieldBackspaceDelegate { 13 | /** 14 | - Parameter textField: A CharacterField instance 15 | */ 16 | func charFieldWillDeleteBackward(_ textField: CharacterField) 17 | } 18 | 19 | class CharacterField: UITextField { 20 | public var willDeleteBackwardDelegate: CharacterFieldBackspaceDelegate? 21 | 22 | override func deleteBackward() { 23 | willDeleteBackwardDelegate?.charFieldWillDeleteBackward(self) 24 | super.deleteBackward() 25 | } 26 | 27 | } 28 | 29 | struct PassCodeInputCell : UIViewRepresentable { 30 | 31 | typealias UIViewType = CharacterField 32 | 33 | // No one else should change this 34 | var index: Int 35 | 36 | // Bound from a PassCodeInputModel instance 37 | @Binding var selectedCellIndex: Int 38 | @Binding var textReference: String 39 | 40 | func makeUIView(context: UIViewRepresentableContext) -> CharacterField { 41 | 42 | let charField = CharacterField(frame: .zero) 43 | charField.textAlignment = .center 44 | 45 | // Caps and suggestions don't make sense 46 | charField.autocapitalizationType = .none 47 | charField.autocorrectionType = .no 48 | 49 | charField.delegate = context.coordinator 50 | charField.willDeleteBackwardDelegate = context.coordinator 51 | 52 | return charField 53 | } 54 | 55 | func updateUIView(_ uiView: CharacterField, 56 | context: UIViewRepresentableContext) { 57 | if index == selectedCellIndex { 58 | uiView.becomeFirstResponder() 59 | } 60 | } 61 | 62 | func makeCoordinator() -> Coordinator { 63 | return Coordinator(index: index, selectedCellIndex: self.$selectedCellIndex, textReference: self.$textReference) 64 | } 65 | 66 | class Coordinator : NSObject, UITextFieldDelegate, CharacterFieldBackspaceDelegate{ 67 | 68 | // No one else should change this 69 | var index: Int 70 | // Each cell will update this 71 | @Binding var selectedCellIndex: Int 72 | // Reference to an index in the text array 73 | // from a PassCodeInputModel instance 74 | @Binding var textReference: String 75 | 76 | /** 77 | - Parameter index: Index of this cell in the pass code array 78 | - Parameter selectedCellIndex: index of where the user is upto 79 | - Parameter textReference: reference in the array to update input 80 | */ 81 | init(index: Int, selectedCellIndex: Binding, 82 | textReference: Binding) { 83 | // The underscore thing is important due to 84 | // the Binding syntax 85 | _selectedCellIndex = selectedCellIndex 86 | _textReference = textReference 87 | self.index = index 88 | } 89 | 90 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { 91 | 92 | let currentText = textField.text ?? "" //textField.text? will almost assuredly never be nil, but we should always assume it could be 93 | guard let stringRange = Range(range, in: currentText) else { return false } 94 | let updatedText = currentText.replacingCharacters(in: stringRange, with: string) 95 | 96 | // Increment the index if the change was on char 97 | if updatedText.count == 1 { 98 | self.selectedCellIndex += 1 99 | } 100 | 101 | // Stop input if there's more than one character 102 | return updatedText.count <= 1 103 | 104 | } 105 | 106 | func textFieldDidChangeSelection(_ textField: UITextField) { 107 | self.textReference = textField.text ?? "" 108 | } 109 | 110 | func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { 111 | self.selectedCellIndex = self.index 112 | return true 113 | } 114 | 115 | func charFieldWillDeleteBackward(_ textField: CharacterField) { 116 | if(textField.text == "" && selectedCellIndex > 0) { 117 | self.selectedCellIndex -= 1 118 | } 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /PassCodeInputDemo/PassCodeInput/PassCodeInputField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassCodeInputField.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 4/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Foundation 11 | 12 | struct PassCodeInputField: View { 13 | 14 | @ObservedObject var inputModel: PassCodeInputModel 15 | 16 | var body: some View { 17 | HStack { 18 | ForEach(0 ..< self.inputModel.numberOfCells) { index in 19 | PassCodeInputCell(index: index, selectedCellIndex: self.$inputModel.selectedCellIndex, textReference: self.$inputModel.passCode[index]) 20 | .frame(height: 20) 21 | .frame(maxWidth: .infinity, alignment: .center) 22 | .padding([.trailing, .leading], 10) 23 | .padding([.vertical], 10) 24 | .overlay( 25 | RoundedRectangle(cornerRadius: 6) 26 | .stroke(Color.red.opacity(0.5), lineWidth: 2) 27 | ) 28 | } 29 | } 30 | } 31 | } 32 | 33 | struct PassCodeInputField_Previews: PreviewProvider { 34 | static var previews: some View { 35 | PassCodeInputField(inputModel: PassCodeInputModel(passCodeLength: 6)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PassCodeInputDemo/PassCodeInput/PassCodeInputModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassCodeInputModel.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 4/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import Combine 12 | 13 | class PassCodeInputModel : ObservableObject { 14 | 15 | @Published var passCode: [String] 16 | @Published var isValid: Bool = false 17 | 18 | @Published var selectedCellIndex: Int { 19 | didSet { 20 | if selectedCellIndex >= self.numberOfCells { 21 | selectedCellIndex = oldValue 22 | } 23 | } 24 | } 25 | 26 | private var cancellableSet: Set = [] 27 | private var passCodeValidPublisher: AnyPublisher { 28 | $passCode 29 | .removeDuplicates() 30 | .map { 31 | $0.allSatisfy { $0.count == 1 } 32 | } 33 | .eraseToAnyPublisher() 34 | } 35 | 36 | /** 37 | - Returns: The number of cells in the pass code 38 | */ 39 | var numberOfCells: Int { 40 | get { 41 | return self.passCode.count 42 | } 43 | } 44 | 45 | /** 46 | - Returns: A String with the current entered code 47 | */ 48 | var passCodeString: String { 49 | get { 50 | return self.passCode.joined() 51 | } 52 | } 53 | 54 | /** 55 | - Parameters passCodeLength: Number of characters in passcode. Must be greater than 0. 56 | */ 57 | init(passCodeLength: UInt) { 58 | 59 | self.passCode = Array() 60 | self.selectedCellIndex = 0 61 | 62 | for _ in 0 ..< passCodeLength { 63 | self.passCode.append("") 64 | } 65 | 66 | passCodeValidPublisher 67 | .receive(on: RunLoop.main) 68 | .assign(to: \.isValid, on: self) 69 | .store(in: &cancellableSet) 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /PassCodeInputDemo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PassCodeInputDemo/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 1/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | 21 | // Create the SwiftUI view that provides the window contents. 22 | let contentView = ContentView() 23 | 24 | // Use a UIHostingController as window root view controller. 25 | if let windowScene = scene as? UIWindowScene { 26 | let window = UIWindow(windowScene: windowScene) 27 | window.rootViewController = UIHostingController(rootView: contentView) 28 | self.window = window 29 | window.makeKeyAndVisible() 30 | } 31 | } 32 | 33 | func sceneDidDisconnect(_ scene: UIScene) { 34 | // Called as the scene is being released by the system. 35 | // This occurs shortly after the scene enters the background, or when its session is discarded. 36 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 37 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 38 | } 39 | 40 | func sceneDidBecomeActive(_ scene: UIScene) { 41 | // Called when the scene has moved from an inactive state to an active state. 42 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 43 | } 44 | 45 | func sceneWillResignActive(_ scene: UIScene) { 46 | // Called when the scene will move from an active state to an inactive state. 47 | // This may occur due to temporary interruptions (ex. an incoming phone call). 48 | } 49 | 50 | func sceneWillEnterForeground(_ scene: UIScene) { 51 | // Called as the scene transitions from the background to the foreground. 52 | // Use this method to undo the changes made on entering the background. 53 | } 54 | 55 | func sceneDidEnterBackground(_ scene: UIScene) { 56 | // Called as the scene transitions from the foreground to the background. 57 | // Use this method to save data, release shared resources, and store enough scene-specific state information 58 | // to restore the scene back to its current state. 59 | } 60 | 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /PassCodeInputDemo/SecondView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondView.swift 3 | // PassCodeInputDemo 4 | // 5 | // Created by Dev Mukherjee on 10/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SecondView: View { 12 | 13 | @ObservedObject var passCodeModel: PassCodeInputModel 14 | 15 | var body: some View { 16 | Form { 17 | Section { 18 | PassCodeInputField(inputModel: self.passCodeModel) 19 | } 20 | Section { 21 | Button(LocalizedStringKey("Prompt_Engage"), action: { 22 | print( 23 | "Passcode is \(self.passCodeModel.passCodeString)" 24 | ) 25 | }).disabled(!self.passCodeModel.isValid) 26 | } 27 | } 28 | } 29 | } 30 | 31 | struct SecondView_Previews: PreviewProvider { 32 | static var previews: some View { 33 | SecondView(passCodeModel: PassCodeInputModel(passCodeLength: 8)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PassCodeInputDemo/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | PassCodeInputDemo 4 | 5 | Created by Dev Mukherjee on 4/5/20. 6 | Copyright © 2020 Anomaly Software. All rights reserved. 7 | */ 8 | 9 | "NestedView" = "Nested View"; 10 | "ModalView" = "Modal View"; 11 | 12 | "Prompt_ShakeOff" = "Shake it Off"; 13 | "Prompt_Engage" = "Engage"; 14 | "First_View" = "First View"; 15 | -------------------------------------------------------------------------------- /PassCodeInputDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PassCodeInputDemoTests/PassCodeInputDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassCodeInputDemoTests.swift 3 | // PassCodeInputDemoTests 4 | // 5 | // Created by Dev Mukherjee on 1/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PassCodeInputDemo 11 | 12 | class PassCodeInputDemoTests: XCTestCase { 13 | 14 | override func setUpWithError() throws { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDownWithError() throws { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() throws { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() throws { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /PassCodeInputDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PassCodeInputDemoUITests/PassCodeInputDemoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PassCodeInputDemoUITests.swift 3 | // PassCodeInputDemoUITests 4 | // 5 | // Created by Dev Mukherjee on 1/5/20. 6 | // Copyright © 2020 Anomaly Software. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class PassCodeInputDemoUITests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDownWithError() throws { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() throws { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() throws { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI PassCode Input 2 | 3 | **Note:** this project is currently under aggressive development, please _do not_ use it in a production application. 4 | 5 | This project provides a Two Factor like Pass Code entry widget. It provides a nice user experience where a user can type in a pass code like input (letters or numbers) and provides bindable validation. 6 | 7 | Features: 8 | - Configurable amount of cells 9 | - Cursor jumps forward as user enters digits 10 | - Cursor jumps backwards as user clears input 11 | - Support for user arbitrarily navigating between cells 12 | - Abstracted data model 13 | - Built for SwiftUI 14 | 15 | Planned Features: 16 | - Restrict input to nominated character sets 17 | - Configurable input boxes 18 | - Group input boxes 19 | 20 | ## Installation 21 | 22 | Vendor in the component: If you wish to vendor the library into your project, simply include the following files from a desired release or `master`: 23 | - `PassCodeModel.swift` 24 | - `PassCodeField.swift` 25 | - `PassCodeCharField.swift` 26 | 27 | Swift Package Manager 28 | 29 | 30 | Running the Demo 31 | 32 | ## Usage 33 | 34 | 35 | ## Getting Help 36 | 37 | Contact the author of the library directly: 38 | - Dev Mukherjee ([@mdevraj](https://twitter.com/mdevraj)) 39 | 40 | For feature requests and bug reports please use [Github issues](https://github.com/devraj/PassCodeInput/issues). 41 | 42 | ## License 43 | 44 | PassCode Input is licensed under terms outlined by the MIT License. 45 | --------------------------------------------------------------------------------