├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── PasscodeLock.podspec ├── PasscodeLock.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── PasscodeLock.xcscheme ├── PasscodeLock ├── Functions.swift ├── Info.plist ├── PasscodeLock.h ├── PasscodeLock │ ├── ChangePasscodeState.swift │ ├── ConfirmPasscodeState.swift │ ├── EnterPasscodeState.swift │ ├── PasscodeLock.swift │ ├── RemovePasscodeState.swift │ └── SetPasscodeState.swift ├── PasscodeLockPresenter.swift ├── PasscodeLockViewController.swift ├── Protocols │ ├── PasscodeLockConfigurationType.swift │ ├── PasscodeLockStateType.swift │ ├── PasscodeLockType.swift │ └── PasscodeRepositoryType.swift ├── Views │ ├── PasscodeLockView.xib │ ├── PasscodeSignButton.swift │ └── PasscodeSignPlaceholderView.swift ├── en.lproj │ └── PasscodeLock.strings └── ru.lproj │ └── PasscodeLock.strings ├── PasscodeLockDemo ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── PasscodeLockConfiguration.swift ├── PasscodeSettingsViewController.swift └── UserDefaultsPasscodeRepository.swift ├── PasscodeLockDemoTests └── Info.plist ├── PasscodeLockDemoUITests └── Info.plist ├── PasscodeLockTests ├── Fakes │ ├── FakePasscodeLock.swift │ ├── FakePasscodeLockConfiguration.swift │ ├── FakePasscodeLockDelegate.swift │ ├── FakePasscodeRepository.swift │ └── FakePasscodeState.swift ├── Info.plist ├── PasscodeLock │ ├── ChangePasscodeStateTests.swift │ ├── ConfirmPasscodeStateTests.swift │ ├── EnterPasscodeStateTests.swift │ ├── PasscodeLockTests.swift │ └── SetPasscodeStateTests.swift └── PasscodeLockTests-Bridging-Header.h ├── README.md ├── passcode-lock.gif └── passcode-view.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X hidden files 2 | .DS_Store 3 | 4 | # Vim swap files 5 | .*.sw? 6 | 7 | # XCode 8 | *.bak 9 | xcuserdata/ 10 | project.xcworkspace 11 | *.xccheckout 12 | DerivedData 13 | 14 | # Sublime 15 | *.sublime-project 16 | *.sublime-workspace 17 | 18 | Carthage/Checkouts 19 | Carthage/Build -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | osx_image: xcode9.3 3 | xcode_project: PasscodeLock.xcodeproj 4 | xcode_scheme: PasscodeLock 5 | script: 6 | - set -o pipefail && xcodebuild test -project PasscodeLock.xcodeproj -scheme "PasscodeLock" -destination "platform=iOS Simulator,name=iPhone 8,OS=latest" ONLY_ACTIVE_ARCH=NO | xcpretty; 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Yanko Dimitrov http://www.yankodimitrov.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /PasscodeLock.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'PasscodeLock' 3 | s.version = '2.0.2' 4 | s.license = { :type => "MIT", :file => 'LICENSE.txt' } 5 | s.summary = 'An iOS passcode lock with Touch ID authentication written in Swift.' 6 | s.homepage = 'https://github.com/zahlz/SwiftPasscodeLock' 7 | s.authors = { 'Yanko Dimitrov' => '', 'moogle19' => 'mail@kseidel.org' } 8 | s.source = { :git => 'https://github.com/zahlz/SwiftPasscodeLock.git' } 9 | 10 | s.ios.deployment_target = '8.0' 11 | 12 | s.source_files = 'PasscodeLock/*.{h,swift}', 13 | 'PasscodeLock/*/*.{swift}' 14 | 15 | s.resources = [ 16 | 'PasscodeLock/Views/PasscodeLockView.xib', 17 | 'PasscodeLock/en.lproj/*' 18 | ] 19 | 20 | s.requires_arc = true 21 | end 22 | -------------------------------------------------------------------------------- /PasscodeLock.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A526E151DA6786200114A5E /* RemovePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A526E141DA6786200114A5E /* RemovePasscodeState.swift */; }; 11 | C99EAF431B90B05700D61E1B /* PasscodeLock.h in Headers */ = {isa = PBXBuildFile; fileRef = C99EAF421B90B05700D61E1B /* PasscodeLock.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | C99EAF4A1B90B05800D61E1B /* PasscodeLock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; }; 13 | C9D3DF0B1B919CE4008561EB /* PasscodeLockView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF0A1B919CE4008561EB /* PasscodeLockView.xib */; }; 14 | C9D3DF131B91AD11008561EB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF121B91AD11008561EB /* AppDelegate.swift */; }; 15 | C9D3DF151B91AD11008561EB /* PasscodeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF141B91AD11008561EB /* PasscodeSettingsViewController.swift */; }; 16 | C9D3DF181B91AD11008561EB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF161B91AD11008561EB /* Main.storyboard */; }; 17 | C9D3DF1A1B91AD11008561EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF191B91AD11008561EB /* Assets.xcassets */; }; 18 | C9D3DF1D1B91AD11008561EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF1B1B91AD11008561EB /* LaunchScreen.storyboard */; }; 19 | C9D3DF3E1B91AD7A008561EB /* PasscodeLock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; }; 20 | C9D3DF3F1B91AD7A008561EB /* PasscodeLock.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 21 | C9D3DF441B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF431B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift */; }; 22 | C9D3DF461B91BD0E008561EB /* PasscodeLockConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF451B91BD0E008561EB /* PasscodeLockConfiguration.swift */; }; 23 | C9D3DF481B91F099008561EB /* PasscodeLockPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF471B91F099008561EB /* PasscodeLockPresenter.swift */; }; 24 | C9DC07D51B90B1F6007A4DD0 /* PasscodeRepositoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */; }; 25 | C9DC07D61B90B1F6007A4DD0 /* PasscodeRepositoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */; }; 26 | C9DC07D81B90B261007A4DD0 /* PasscodeLockStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */; }; 27 | C9DC07D91B90B261007A4DD0 /* PasscodeLockStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */; }; 28 | C9DC07DB1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */; }; 29 | C9DC07DC1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */; }; 30 | C9DC07DE1B90BA06007A4DD0 /* PasscodeLockType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */; }; 31 | C9DC07DF1B90BA06007A4DD0 /* PasscodeLockType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */; }; 32 | C9DC07E11B90BBFD007A4DD0 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */; }; 33 | C9DC07E21B90BBFD007A4DD0 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */; }; 34 | C9DC07E51B90BCF9007A4DD0 /* PasscodeLockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E41B90BCF9007A4DD0 /* PasscodeLockTests.swift */; }; 35 | C9DC07E71B90C382007A4DD0 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9DC07E61B90C382007A4DD0 /* LocalAuthentication.framework */; }; 36 | C9DC07EA1B90C690007A4DD0 /* FakePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E91B90C690007A4DD0 /* FakePasscodeState.swift */; }; 37 | C9DC07EC1B90C72A007A4DD0 /* FakePasscodeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07EB1B90C72A007A4DD0 /* FakePasscodeRepository.swift */; }; 38 | C9DC07EE1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07ED1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift */; }; 39 | C9DC07F01B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07EF1B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift */; }; 40 | C9DC07F21B90C9DE007A4DD0 /* EnterPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */; }; 41 | C9DC07F31B90C9DE007A4DD0 /* EnterPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */; }; 42 | C9DC07F81B90CF29007A4DD0 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F71B90CF29007A4DD0 /* Functions.swift */; }; 43 | C9DC07F91B90CF29007A4DD0 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F71B90CF29007A4DD0 /* Functions.swift */; }; 44 | C9DC07FC1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */; }; 45 | C9DC07FD1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */; }; 46 | C9DC07FF1B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */; }; 47 | C9DC08001B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */; }; 48 | C9DC08021B90D2BA007A4DD0 /* ConfirmPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */; }; 49 | C9DC08031B90D2BA007A4DD0 /* ConfirmPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */; }; 50 | C9DC08051B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */; }; 51 | C9DC08061B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */; }; 52 | C9DC08081B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08071B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift */; }; 53 | C9DC080A1B90DB09007A4DD0 /* FakePasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08091B90DB09007A4DD0 /* FakePasscodeLock.swift */; }; 54 | C9DC080C1B90DC38007A4DD0 /* SetPasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC080B1B90DC38007A4DD0 /* SetPasscodeStateTests.swift */; }; 55 | C9DC080E1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC080D1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift */; }; 56 | C9DC08101B90DD91007A4DD0 /* ChangePasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC080F1B90DD91007A4DD0 /* ChangePasscodeStateTests.swift */; }; 57 | C9DC08121B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08111B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift */; }; 58 | C9DC08141B90DE50007A4DD0 /* PasscodeSignButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08131B90DE50007A4DD0 /* PasscodeSignButton.swift */; }; 59 | C9DC08161B90DF4E007A4DD0 /* PasscodeLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08151B90DF4E007A4DD0 /* PasscodeLockViewController.swift */; }; 60 | /* End PBXBuildFile section */ 61 | 62 | /* Begin PBXContainerItemProxy section */ 63 | C99EAF4B1B90B05800D61E1B /* PBXContainerItemProxy */ = { 64 | isa = PBXContainerItemProxy; 65 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 66 | proxyType = 1; 67 | remoteGlobalIDString = C99EAF3E1B90B05700D61E1B; 68 | remoteInfo = PasscodeLock; 69 | }; 70 | C9D3DF241B91AD12008561EB /* PBXContainerItemProxy */ = { 71 | isa = PBXContainerItemProxy; 72 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 73 | proxyType = 1; 74 | remoteGlobalIDString = C9D3DF0F1B91AD11008561EB; 75 | remoteInfo = PasscodeLockDemo; 76 | }; 77 | C9D3DF2F1B91AD12008561EB /* PBXContainerItemProxy */ = { 78 | isa = PBXContainerItemProxy; 79 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 80 | proxyType = 1; 81 | remoteGlobalIDString = C9D3DF0F1B91AD11008561EB; 82 | remoteInfo = PasscodeLockDemo; 83 | }; 84 | C9D3DF401B91AD7A008561EB /* PBXContainerItemProxy */ = { 85 | isa = PBXContainerItemProxy; 86 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 87 | proxyType = 1; 88 | remoteGlobalIDString = C99EAF3E1B90B05700D61E1B; 89 | remoteInfo = PasscodeLock; 90 | }; 91 | /* End PBXContainerItemProxy section */ 92 | 93 | /* Begin PBXCopyFilesBuildPhase section */ 94 | C9D3DF421B91AD7A008561EB /* Embed Frameworks */ = { 95 | isa = PBXCopyFilesBuildPhase; 96 | buildActionMask = 2147483647; 97 | dstPath = ""; 98 | dstSubfolderSpec = 10; 99 | files = ( 100 | C9D3DF3F1B91AD7A008561EB /* PasscodeLock.framework in Embed Frameworks */, 101 | ); 102 | name = "Embed Frameworks"; 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | /* End PBXCopyFilesBuildPhase section */ 106 | 107 | /* Begin PBXFileReference section */ 108 | 0A526E141DA6786200114A5E /* RemovePasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemovePasscodeState.swift; sourceTree = ""; }; 109 | C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PasscodeLock.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 110 | C99EAF421B90B05700D61E1B /* PasscodeLock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PasscodeLock.h; sourceTree = ""; }; 111 | C99EAF441B90B05700D61E1B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 112 | C99EAF491B90B05800D61E1B /* PasscodeLockTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PasscodeLockTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 113 | C99EAF501B90B05800D61E1B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 114 | C9D3DF0A1B919CE4008561EB /* PasscodeLockView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PasscodeLockView.xib; sourceTree = ""; }; 115 | C9D3DF101B91AD11008561EB /* PasscodeLockDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PasscodeLockDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 116 | C9D3DF121B91AD11008561EB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 117 | C9D3DF141B91AD11008561EB /* PasscodeSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasscodeSettingsViewController.swift; sourceTree = ""; }; 118 | C9D3DF171B91AD11008561EB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 119 | C9D3DF191B91AD11008561EB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 120 | C9D3DF1C1B91AD11008561EB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 121 | C9D3DF1E1B91AD11008561EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 122 | C9D3DF231B91AD11008561EB /* PasscodeLockDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PasscodeLockDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 123 | C9D3DF291B91AD12008561EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 124 | C9D3DF2E1B91AD12008561EB /* PasscodeLockDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PasscodeLockDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 125 | C9D3DF341B91AD12008561EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 126 | C9D3DF431B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsPasscodeRepository.swift; sourceTree = ""; }; 127 | C9D3DF451B91BD0E008561EB /* PasscodeLockConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockConfiguration.swift; sourceTree = ""; }; 128 | C9D3DF471B91F099008561EB /* PasscodeLockPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockPresenter.swift; sourceTree = ""; }; 129 | C9DC07CE1B90B0AC007A4DD0 /* PasscodeLockTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PasscodeLockTests-Bridging-Header.h"; sourceTree = ""; }; 130 | C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeRepositoryType.swift; sourceTree = ""; }; 131 | C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockStateType.swift; sourceTree = ""; }; 132 | C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockConfigurationType.swift; sourceTree = ""; }; 133 | C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockType.swift; sourceTree = ""; }; 134 | C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLock.swift; sourceTree = ""; }; 135 | C9DC07E41B90BCF9007A4DD0 /* PasscodeLockTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockTests.swift; sourceTree = ""; }; 136 | C9DC07E61B90C382007A4DD0 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; 137 | C9DC07E91B90C690007A4DD0 /* FakePasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePasscodeState.swift; sourceTree = ""; }; 138 | C9DC07EB1B90C72A007A4DD0 /* FakePasscodeRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePasscodeRepository.swift; sourceTree = ""; }; 139 | C9DC07ED1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePasscodeLockConfiguration.swift; sourceTree = ""; }; 140 | C9DC07EF1B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePasscodeLockDelegate.swift; sourceTree = ""; }; 141 | C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasscodeState.swift; sourceTree = ""; }; 142 | C9DC07F71B90CF29007A4DD0 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; 143 | C9DC07FB1B90D0A3007A4DD0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/PasscodeLock.strings; sourceTree = ""; }; 144 | C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetPasscodeState.swift; sourceTree = ""; }; 145 | C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmPasscodeState.swift; sourceTree = ""; }; 146 | C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePasscodeState.swift; sourceTree = ""; }; 147 | C9DC08071B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterPasscodeStateTests.swift; sourceTree = ""; }; 148 | C9DC08091B90DB09007A4DD0 /* FakePasscodeLock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePasscodeLock.swift; sourceTree = ""; }; 149 | C9DC080B1B90DC38007A4DD0 /* SetPasscodeStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetPasscodeStateTests.swift; sourceTree = ""; }; 150 | C9DC080D1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmPasscodeStateTests.swift; sourceTree = ""; }; 151 | C9DC080F1B90DD91007A4DD0 /* ChangePasscodeStateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePasscodeStateTests.swift; sourceTree = ""; }; 152 | C9DC08111B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeSignPlaceholderView.swift; sourceTree = ""; }; 153 | C9DC08131B90DE50007A4DD0 /* PasscodeSignButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeSignButton.swift; sourceTree = ""; }; 154 | C9DC08151B90DF4E007A4DD0 /* PasscodeLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasscodeLockViewController.swift; sourceTree = ""; }; 155 | /* End PBXFileReference section */ 156 | 157 | /* Begin PBXFrameworksBuildPhase section */ 158 | C99EAF3B1B90B05700D61E1B /* Frameworks */ = { 159 | isa = PBXFrameworksBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | C9DC07E71B90C382007A4DD0 /* LocalAuthentication.framework in Frameworks */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | C99EAF461B90B05700D61E1B /* Frameworks */ = { 167 | isa = PBXFrameworksBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | C99EAF4A1B90B05800D61E1B /* PasscodeLock.framework in Frameworks */, 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | C9D3DF0D1B91AD11008561EB /* Frameworks */ = { 175 | isa = PBXFrameworksBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | C9D3DF3E1B91AD7A008561EB /* PasscodeLock.framework in Frameworks */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | C9D3DF201B91AD11008561EB /* Frameworks */ = { 183 | isa = PBXFrameworksBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | C9D3DF2B1B91AD12008561EB /* Frameworks */ = { 190 | isa = PBXFrameworksBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXFrameworksBuildPhase section */ 197 | 198 | /* Begin PBXGroup section */ 199 | C99EAF351B90B05700D61E1B = { 200 | isa = PBXGroup; 201 | children = ( 202 | C9DC07E61B90C382007A4DD0 /* LocalAuthentication.framework */, 203 | C99EAF411B90B05700D61E1B /* PasscodeLock */, 204 | C99EAF4D1B90B05800D61E1B /* PasscodeLockTests */, 205 | C9D3DF111B91AD11008561EB /* PasscodeLockDemo */, 206 | C9D3DF261B91AD12008561EB /* PasscodeLockDemoTests */, 207 | C9D3DF311B91AD12008561EB /* PasscodeLockDemoUITests */, 208 | C99EAF401B90B05700D61E1B /* Products */, 209 | ); 210 | sourceTree = ""; 211 | }; 212 | C99EAF401B90B05700D61E1B /* Products */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */, 216 | C99EAF491B90B05800D61E1B /* PasscodeLockTests.xctest */, 217 | C9D3DF101B91AD11008561EB /* PasscodeLockDemo.app */, 218 | C9D3DF231B91AD11008561EB /* PasscodeLockDemoTests.xctest */, 219 | C9D3DF2E1B91AD12008561EB /* PasscodeLockDemoUITests.xctest */, 220 | ); 221 | name = Products; 222 | sourceTree = ""; 223 | }; 224 | C99EAF411B90B05700D61E1B /* PasscodeLock */ = { 225 | isa = PBXGroup; 226 | children = ( 227 | C9DC07D21B90B1CE007A4DD0 /* Protocols */, 228 | C9DC07D11B90B1CE007A4DD0 /* PasscodeLock */, 229 | C9DC07D31B90B1CE007A4DD0 /* Views */, 230 | C99EAF421B90B05700D61E1B /* PasscodeLock.h */, 231 | C99EAF441B90B05700D61E1B /* Info.plist */, 232 | C9DC07F71B90CF29007A4DD0 /* Functions.swift */, 233 | C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */, 234 | C9DC08151B90DF4E007A4DD0 /* PasscodeLockViewController.swift */, 235 | C9D3DF471B91F099008561EB /* PasscodeLockPresenter.swift */, 236 | ); 237 | path = PasscodeLock; 238 | sourceTree = ""; 239 | }; 240 | C99EAF4D1B90B05800D61E1B /* PasscodeLockTests */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | C9DC07E81B90C673007A4DD0 /* Fakes */, 244 | C9DC07E31B90BCE7007A4DD0 /* PasscodeLock */, 245 | C99EAF501B90B05800D61E1B /* Info.plist */, 246 | C9DC07CE1B90B0AC007A4DD0 /* PasscodeLockTests-Bridging-Header.h */, 247 | ); 248 | path = PasscodeLockTests; 249 | sourceTree = ""; 250 | }; 251 | C9D3DF111B91AD11008561EB /* PasscodeLockDemo */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | C9D3DF121B91AD11008561EB /* AppDelegate.swift */, 255 | C9D3DF141B91AD11008561EB /* PasscodeSettingsViewController.swift */, 256 | C9D3DF161B91AD11008561EB /* Main.storyboard */, 257 | C9D3DF191B91AD11008561EB /* Assets.xcassets */, 258 | C9D3DF1B1B91AD11008561EB /* LaunchScreen.storyboard */, 259 | C9D3DF1E1B91AD11008561EB /* Info.plist */, 260 | C9D3DF431B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift */, 261 | C9D3DF451B91BD0E008561EB /* PasscodeLockConfiguration.swift */, 262 | ); 263 | path = PasscodeLockDemo; 264 | sourceTree = ""; 265 | }; 266 | C9D3DF261B91AD12008561EB /* PasscodeLockDemoTests */ = { 267 | isa = PBXGroup; 268 | children = ( 269 | C9D3DF291B91AD12008561EB /* Info.plist */, 270 | ); 271 | path = PasscodeLockDemoTests; 272 | sourceTree = ""; 273 | }; 274 | C9D3DF311B91AD12008561EB /* PasscodeLockDemoUITests */ = { 275 | isa = PBXGroup; 276 | children = ( 277 | C9D3DF341B91AD12008561EB /* Info.plist */, 278 | ); 279 | path = PasscodeLockDemoUITests; 280 | sourceTree = ""; 281 | }; 282 | C9DC07D11B90B1CE007A4DD0 /* PasscodeLock */ = { 283 | isa = PBXGroup; 284 | children = ( 285 | C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */, 286 | C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */, 287 | C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */, 288 | C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */, 289 | C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */, 290 | 0A526E141DA6786200114A5E /* RemovePasscodeState.swift */, 291 | ); 292 | path = PasscodeLock; 293 | sourceTree = ""; 294 | }; 295 | C9DC07D21B90B1CE007A4DD0 /* Protocols */ = { 296 | isa = PBXGroup; 297 | children = ( 298 | C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */, 299 | C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */, 300 | C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */, 301 | C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */, 302 | ); 303 | path = Protocols; 304 | sourceTree = ""; 305 | }; 306 | C9DC07D31B90B1CE007A4DD0 /* Views */ = { 307 | isa = PBXGroup; 308 | children = ( 309 | C9DC08111B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift */, 310 | C9DC08131B90DE50007A4DD0 /* PasscodeSignButton.swift */, 311 | C9D3DF0A1B919CE4008561EB /* PasscodeLockView.xib */, 312 | ); 313 | path = Views; 314 | sourceTree = ""; 315 | }; 316 | C9DC07E31B90BCE7007A4DD0 /* PasscodeLock */ = { 317 | isa = PBXGroup; 318 | children = ( 319 | C9DC07E41B90BCF9007A4DD0 /* PasscodeLockTests.swift */, 320 | C9DC08071B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift */, 321 | C9DC080B1B90DC38007A4DD0 /* SetPasscodeStateTests.swift */, 322 | C9DC080D1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift */, 323 | C9DC080F1B90DD91007A4DD0 /* ChangePasscodeStateTests.swift */, 324 | ); 325 | path = PasscodeLock; 326 | sourceTree = ""; 327 | }; 328 | C9DC07E81B90C673007A4DD0 /* Fakes */ = { 329 | isa = PBXGroup; 330 | children = ( 331 | C9DC07E91B90C690007A4DD0 /* FakePasscodeState.swift */, 332 | C9DC07EB1B90C72A007A4DD0 /* FakePasscodeRepository.swift */, 333 | C9DC07ED1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift */, 334 | C9DC07EF1B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift */, 335 | C9DC08091B90DB09007A4DD0 /* FakePasscodeLock.swift */, 336 | ); 337 | path = Fakes; 338 | sourceTree = ""; 339 | }; 340 | /* End PBXGroup section */ 341 | 342 | /* Begin PBXHeadersBuildPhase section */ 343 | C99EAF3C1B90B05700D61E1B /* Headers */ = { 344 | isa = PBXHeadersBuildPhase; 345 | buildActionMask = 2147483647; 346 | files = ( 347 | C99EAF431B90B05700D61E1B /* PasscodeLock.h in Headers */, 348 | ); 349 | runOnlyForDeploymentPostprocessing = 0; 350 | }; 351 | /* End PBXHeadersBuildPhase section */ 352 | 353 | /* Begin PBXNativeTarget section */ 354 | C99EAF3E1B90B05700D61E1B /* PasscodeLock */ = { 355 | isa = PBXNativeTarget; 356 | buildConfigurationList = C99EAF531B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLock" */; 357 | buildPhases = ( 358 | C99EAF3A1B90B05700D61E1B /* Sources */, 359 | C99EAF3B1B90B05700D61E1B /* Frameworks */, 360 | C99EAF3C1B90B05700D61E1B /* Headers */, 361 | C99EAF3D1B90B05700D61E1B /* Resources */, 362 | ); 363 | buildRules = ( 364 | ); 365 | dependencies = ( 366 | ); 367 | name = PasscodeLock; 368 | productName = PasscodeLock; 369 | productReference = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; 370 | productType = "com.apple.product-type.framework"; 371 | }; 372 | C99EAF481B90B05700D61E1B /* PasscodeLockTests */ = { 373 | isa = PBXNativeTarget; 374 | buildConfigurationList = C99EAF561B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLockTests" */; 375 | buildPhases = ( 376 | C99EAF451B90B05700D61E1B /* Sources */, 377 | C99EAF461B90B05700D61E1B /* Frameworks */, 378 | C99EAF471B90B05700D61E1B /* Resources */, 379 | ); 380 | buildRules = ( 381 | ); 382 | dependencies = ( 383 | C99EAF4C1B90B05800D61E1B /* PBXTargetDependency */, 384 | ); 385 | name = PasscodeLockTests; 386 | productName = PasscodeLockTests; 387 | productReference = C99EAF491B90B05800D61E1B /* PasscodeLockTests.xctest */; 388 | productType = "com.apple.product-type.bundle.unit-test"; 389 | }; 390 | C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */ = { 391 | isa = PBXNativeTarget; 392 | buildConfigurationList = C9D3DF351B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemo" */; 393 | buildPhases = ( 394 | C9D3DF0C1B91AD11008561EB /* Sources */, 395 | C9D3DF0D1B91AD11008561EB /* Frameworks */, 396 | C9D3DF0E1B91AD11008561EB /* Resources */, 397 | C9D3DF421B91AD7A008561EB /* Embed Frameworks */, 398 | ); 399 | buildRules = ( 400 | ); 401 | dependencies = ( 402 | C9D3DF411B91AD7A008561EB /* PBXTargetDependency */, 403 | ); 404 | name = PasscodeLockDemo; 405 | productName = PasscodeLockDemo; 406 | productReference = C9D3DF101B91AD11008561EB /* PasscodeLockDemo.app */; 407 | productType = "com.apple.product-type.application"; 408 | }; 409 | C9D3DF221B91AD11008561EB /* PasscodeLockDemoTests */ = { 410 | isa = PBXNativeTarget; 411 | buildConfigurationList = C9D3DF381B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoTests" */; 412 | buildPhases = ( 413 | C9D3DF1F1B91AD11008561EB /* Sources */, 414 | C9D3DF201B91AD11008561EB /* Frameworks */, 415 | C9D3DF211B91AD11008561EB /* Resources */, 416 | ); 417 | buildRules = ( 418 | ); 419 | dependencies = ( 420 | C9D3DF251B91AD12008561EB /* PBXTargetDependency */, 421 | ); 422 | name = PasscodeLockDemoTests; 423 | productName = PasscodeLockDemoTests; 424 | productReference = C9D3DF231B91AD11008561EB /* PasscodeLockDemoTests.xctest */; 425 | productType = "com.apple.product-type.bundle.unit-test"; 426 | }; 427 | C9D3DF2D1B91AD12008561EB /* PasscodeLockDemoUITests */ = { 428 | isa = PBXNativeTarget; 429 | buildConfigurationList = C9D3DF3B1B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoUITests" */; 430 | buildPhases = ( 431 | C9D3DF2A1B91AD12008561EB /* Sources */, 432 | C9D3DF2B1B91AD12008561EB /* Frameworks */, 433 | C9D3DF2C1B91AD12008561EB /* Resources */, 434 | ); 435 | buildRules = ( 436 | ); 437 | dependencies = ( 438 | C9D3DF301B91AD12008561EB /* PBXTargetDependency */, 439 | ); 440 | name = PasscodeLockDemoUITests; 441 | productName = PasscodeLockDemoUITests; 442 | productReference = C9D3DF2E1B91AD12008561EB /* PasscodeLockDemoUITests.xctest */; 443 | productType = "com.apple.product-type.bundle.ui-testing"; 444 | }; 445 | /* End PBXNativeTarget section */ 446 | 447 | /* Begin PBXProject section */ 448 | C99EAF361B90B05700D61E1B /* Project object */ = { 449 | isa = PBXProject; 450 | attributes = { 451 | LastSwiftUpdateCheck = 0700; 452 | LastUpgradeCheck = 1020; 453 | ORGANIZATIONNAME = "Yanko Dimitrov"; 454 | TargetAttributes = { 455 | C99EAF3E1B90B05700D61E1B = { 456 | CreatedOnToolsVersion = 7.0; 457 | LastSwiftMigration = 1020; 458 | }; 459 | C99EAF481B90B05700D61E1B = { 460 | CreatedOnToolsVersion = 7.0; 461 | LastSwiftMigration = 0940; 462 | }; 463 | C9D3DF0F1B91AD11008561EB = { 464 | CreatedOnToolsVersion = 7.0; 465 | DevelopmentTeam = 6HPG35RJ6Y; 466 | LastSwiftMigration = 1020; 467 | ProvisioningStyle = Automatic; 468 | }; 469 | C9D3DF221B91AD11008561EB = { 470 | CreatedOnToolsVersion = 7.0; 471 | LastSwiftMigration = 0800; 472 | TestTargetID = C9D3DF0F1B91AD11008561EB; 473 | }; 474 | C9D3DF2D1B91AD12008561EB = { 475 | CreatedOnToolsVersion = 7.0; 476 | LastSwiftMigration = 0800; 477 | TestTargetID = C9D3DF0F1B91AD11008561EB; 478 | }; 479 | }; 480 | }; 481 | buildConfigurationList = C99EAF391B90B05700D61E1B /* Build configuration list for PBXProject "PasscodeLock" */; 482 | compatibilityVersion = "Xcode 9.3"; 483 | developmentRegion = en; 484 | hasScannedForEncodings = 0; 485 | knownRegions = ( 486 | en, 487 | Base, 488 | ); 489 | mainGroup = C99EAF351B90B05700D61E1B; 490 | productRefGroup = C99EAF401B90B05700D61E1B /* Products */; 491 | projectDirPath = ""; 492 | projectRoot = ""; 493 | targets = ( 494 | C99EAF3E1B90B05700D61E1B /* PasscodeLock */, 495 | C99EAF481B90B05700D61E1B /* PasscodeLockTests */, 496 | C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */, 497 | C9D3DF221B91AD11008561EB /* PasscodeLockDemoTests */, 498 | C9D3DF2D1B91AD12008561EB /* PasscodeLockDemoUITests */, 499 | ); 500 | }; 501 | /* End PBXProject section */ 502 | 503 | /* Begin PBXResourcesBuildPhase section */ 504 | C99EAF3D1B90B05700D61E1B /* Resources */ = { 505 | isa = PBXResourcesBuildPhase; 506 | buildActionMask = 2147483647; 507 | files = ( 508 | C9DC07FC1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */, 509 | C9D3DF0B1B919CE4008561EB /* PasscodeLockView.xib in Resources */, 510 | ); 511 | runOnlyForDeploymentPostprocessing = 0; 512 | }; 513 | C99EAF471B90B05700D61E1B /* Resources */ = { 514 | isa = PBXResourcesBuildPhase; 515 | buildActionMask = 2147483647; 516 | files = ( 517 | C9DC07FD1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */, 518 | ); 519 | runOnlyForDeploymentPostprocessing = 0; 520 | }; 521 | C9D3DF0E1B91AD11008561EB /* Resources */ = { 522 | isa = PBXResourcesBuildPhase; 523 | buildActionMask = 2147483647; 524 | files = ( 525 | C9D3DF1D1B91AD11008561EB /* LaunchScreen.storyboard in Resources */, 526 | C9D3DF1A1B91AD11008561EB /* Assets.xcassets in Resources */, 527 | C9D3DF181B91AD11008561EB /* Main.storyboard in Resources */, 528 | ); 529 | runOnlyForDeploymentPostprocessing = 0; 530 | }; 531 | C9D3DF211B91AD11008561EB /* Resources */ = { 532 | isa = PBXResourcesBuildPhase; 533 | buildActionMask = 2147483647; 534 | files = ( 535 | ); 536 | runOnlyForDeploymentPostprocessing = 0; 537 | }; 538 | C9D3DF2C1B91AD12008561EB /* Resources */ = { 539 | isa = PBXResourcesBuildPhase; 540 | buildActionMask = 2147483647; 541 | files = ( 542 | ); 543 | runOnlyForDeploymentPostprocessing = 0; 544 | }; 545 | /* End PBXResourcesBuildPhase section */ 546 | 547 | /* Begin PBXSourcesBuildPhase section */ 548 | C99EAF3A1B90B05700D61E1B /* Sources */ = { 549 | isa = PBXSourcesBuildPhase; 550 | buildActionMask = 2147483647; 551 | files = ( 552 | C9DC07FF1B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */, 553 | C9DC08051B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */, 554 | C9DC08021B90D2BA007A4DD0 /* ConfirmPasscodeState.swift in Sources */, 555 | 0A526E151DA6786200114A5E /* RemovePasscodeState.swift in Sources */, 556 | C9DC08121B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift in Sources */, 557 | C9DC07F21B90C9DE007A4DD0 /* EnterPasscodeState.swift in Sources */, 558 | C9DC08141B90DE50007A4DD0 /* PasscodeSignButton.swift in Sources */, 559 | C9D3DF481B91F099008561EB /* PasscodeLockPresenter.swift in Sources */, 560 | C9DC07D81B90B261007A4DD0 /* PasscodeLockStateType.swift in Sources */, 561 | C9DC08161B90DF4E007A4DD0 /* PasscodeLockViewController.swift in Sources */, 562 | C9DC07E11B90BBFD007A4DD0 /* PasscodeLock.swift in Sources */, 563 | C9DC07F81B90CF29007A4DD0 /* Functions.swift in Sources */, 564 | C9DC07DB1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift in Sources */, 565 | C9DC07DE1B90BA06007A4DD0 /* PasscodeLockType.swift in Sources */, 566 | C9DC07D51B90B1F6007A4DD0 /* PasscodeRepositoryType.swift in Sources */, 567 | ); 568 | runOnlyForDeploymentPostprocessing = 0; 569 | }; 570 | C99EAF451B90B05700D61E1B /* Sources */ = { 571 | isa = PBXSourcesBuildPhase; 572 | buildActionMask = 2147483647; 573 | files = ( 574 | C9DC07EC1B90C72A007A4DD0 /* FakePasscodeRepository.swift in Sources */, 575 | C9DC08061B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */, 576 | C9DC080E1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift in Sources */, 577 | C9DC07E51B90BCF9007A4DD0 /* PasscodeLockTests.swift in Sources */, 578 | C9DC08031B90D2BA007A4DD0 /* ConfirmPasscodeState.swift in Sources */, 579 | C9DC07F01B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift in Sources */, 580 | C9DC07D91B90B261007A4DD0 /* PasscodeLockStateType.swift in Sources */, 581 | C9DC07E21B90BBFD007A4DD0 /* PasscodeLock.swift in Sources */, 582 | C9DC07EA1B90C690007A4DD0 /* FakePasscodeState.swift in Sources */, 583 | C9DC08101B90DD91007A4DD0 /* ChangePasscodeStateTests.swift in Sources */, 584 | C9DC07DC1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift in Sources */, 585 | C9DC07F31B90C9DE007A4DD0 /* EnterPasscodeState.swift in Sources */, 586 | C9DC08001B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */, 587 | C9DC07EE1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift in Sources */, 588 | C9DC080C1B90DC38007A4DD0 /* SetPasscodeStateTests.swift in Sources */, 589 | C9DC08081B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift in Sources */, 590 | C9DC07DF1B90BA06007A4DD0 /* PasscodeLockType.swift in Sources */, 591 | C9DC07D61B90B1F6007A4DD0 /* PasscodeRepositoryType.swift in Sources */, 592 | C9DC07F91B90CF29007A4DD0 /* Functions.swift in Sources */, 593 | C9DC080A1B90DB09007A4DD0 /* FakePasscodeLock.swift in Sources */, 594 | ); 595 | runOnlyForDeploymentPostprocessing = 0; 596 | }; 597 | C9D3DF0C1B91AD11008561EB /* Sources */ = { 598 | isa = PBXSourcesBuildPhase; 599 | buildActionMask = 2147483647; 600 | files = ( 601 | C9D3DF441B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift in Sources */, 602 | C9D3DF461B91BD0E008561EB /* PasscodeLockConfiguration.swift in Sources */, 603 | C9D3DF151B91AD11008561EB /* PasscodeSettingsViewController.swift in Sources */, 604 | C9D3DF131B91AD11008561EB /* AppDelegate.swift in Sources */, 605 | ); 606 | runOnlyForDeploymentPostprocessing = 0; 607 | }; 608 | C9D3DF1F1B91AD11008561EB /* Sources */ = { 609 | isa = PBXSourcesBuildPhase; 610 | buildActionMask = 2147483647; 611 | files = ( 612 | ); 613 | runOnlyForDeploymentPostprocessing = 0; 614 | }; 615 | C9D3DF2A1B91AD12008561EB /* Sources */ = { 616 | isa = PBXSourcesBuildPhase; 617 | buildActionMask = 2147483647; 618 | files = ( 619 | ); 620 | runOnlyForDeploymentPostprocessing = 0; 621 | }; 622 | /* End PBXSourcesBuildPhase section */ 623 | 624 | /* Begin PBXTargetDependency section */ 625 | C99EAF4C1B90B05800D61E1B /* PBXTargetDependency */ = { 626 | isa = PBXTargetDependency; 627 | target = C99EAF3E1B90B05700D61E1B /* PasscodeLock */; 628 | targetProxy = C99EAF4B1B90B05800D61E1B /* PBXContainerItemProxy */; 629 | }; 630 | C9D3DF251B91AD12008561EB /* PBXTargetDependency */ = { 631 | isa = PBXTargetDependency; 632 | target = C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */; 633 | targetProxy = C9D3DF241B91AD12008561EB /* PBXContainerItemProxy */; 634 | }; 635 | C9D3DF301B91AD12008561EB /* PBXTargetDependency */ = { 636 | isa = PBXTargetDependency; 637 | target = C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */; 638 | targetProxy = C9D3DF2F1B91AD12008561EB /* PBXContainerItemProxy */; 639 | }; 640 | C9D3DF411B91AD7A008561EB /* PBXTargetDependency */ = { 641 | isa = PBXTargetDependency; 642 | target = C99EAF3E1B90B05700D61E1B /* PasscodeLock */; 643 | targetProxy = C9D3DF401B91AD7A008561EB /* PBXContainerItemProxy */; 644 | }; 645 | /* End PBXTargetDependency section */ 646 | 647 | /* Begin PBXVariantGroup section */ 648 | C9D3DF161B91AD11008561EB /* Main.storyboard */ = { 649 | isa = PBXVariantGroup; 650 | children = ( 651 | C9D3DF171B91AD11008561EB /* Base */, 652 | ); 653 | name = Main.storyboard; 654 | sourceTree = ""; 655 | }; 656 | C9D3DF1B1B91AD11008561EB /* LaunchScreen.storyboard */ = { 657 | isa = PBXVariantGroup; 658 | children = ( 659 | C9D3DF1C1B91AD11008561EB /* Base */, 660 | ); 661 | name = LaunchScreen.storyboard; 662 | sourceTree = ""; 663 | }; 664 | C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */ = { 665 | isa = PBXVariantGroup; 666 | children = ( 667 | C9DC07FB1B90D0A3007A4DD0 /* en */, 668 | ); 669 | name = PasscodeLock.strings; 670 | sourceTree = ""; 671 | }; 672 | /* End PBXVariantGroup section */ 673 | 674 | /* Begin XCBuildConfiguration section */ 675 | C99EAF511B90B05800D61E1B /* Debug */ = { 676 | isa = XCBuildConfiguration; 677 | buildSettings = { 678 | ALWAYS_SEARCH_USER_PATHS = NO; 679 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 680 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 681 | CLANG_CXX_LIBRARY = "libc++"; 682 | CLANG_ENABLE_MODULES = YES; 683 | CLANG_ENABLE_OBJC_ARC = YES; 684 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 685 | CLANG_WARN_BOOL_CONVERSION = YES; 686 | CLANG_WARN_COMMA = YES; 687 | CLANG_WARN_CONSTANT_CONVERSION = YES; 688 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 689 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 690 | CLANG_WARN_EMPTY_BODY = YES; 691 | CLANG_WARN_ENUM_CONVERSION = YES; 692 | CLANG_WARN_INFINITE_RECURSION = YES; 693 | CLANG_WARN_INT_CONVERSION = YES; 694 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 695 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 696 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 697 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 698 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 699 | CLANG_WARN_STRICT_PROTOTYPES = YES; 700 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 701 | CLANG_WARN_UNREACHABLE_CODE = YES; 702 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 703 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 704 | COPY_PHASE_STRIP = NO; 705 | CURRENT_PROJECT_VERSION = 1; 706 | DEBUG_INFORMATION_FORMAT = dwarf; 707 | ENABLE_STRICT_OBJC_MSGSEND = YES; 708 | ENABLE_TESTABILITY = YES; 709 | GCC_C_LANGUAGE_STANDARD = gnu99; 710 | GCC_DYNAMIC_NO_PIC = NO; 711 | GCC_NO_COMMON_BLOCKS = YES; 712 | GCC_OPTIMIZATION_LEVEL = 0; 713 | GCC_PREPROCESSOR_DEFINITIONS = ( 714 | "DEBUG=1", 715 | "$(inherited)", 716 | ); 717 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 718 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 719 | GCC_WARN_UNDECLARED_SELECTOR = YES; 720 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 721 | GCC_WARN_UNUSED_FUNCTION = YES; 722 | GCC_WARN_UNUSED_VARIABLE = YES; 723 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 724 | MTL_ENABLE_DEBUG_INFO = YES; 725 | ONLY_ACTIVE_ARCH = YES; 726 | SDKROOT = iphoneos; 727 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 728 | SWIFT_VERSION = 4.0; 729 | TARGETED_DEVICE_FAMILY = "1,2"; 730 | VERSIONING_SYSTEM = "apple-generic"; 731 | VERSION_INFO_PREFIX = ""; 732 | }; 733 | name = Debug; 734 | }; 735 | C99EAF521B90B05800D61E1B /* Release */ = { 736 | isa = XCBuildConfiguration; 737 | buildSettings = { 738 | ALWAYS_SEARCH_USER_PATHS = NO; 739 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 740 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 741 | CLANG_CXX_LIBRARY = "libc++"; 742 | CLANG_ENABLE_MODULES = YES; 743 | CLANG_ENABLE_OBJC_ARC = YES; 744 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 745 | CLANG_WARN_BOOL_CONVERSION = YES; 746 | CLANG_WARN_COMMA = YES; 747 | CLANG_WARN_CONSTANT_CONVERSION = YES; 748 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 749 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 750 | CLANG_WARN_EMPTY_BODY = YES; 751 | CLANG_WARN_ENUM_CONVERSION = YES; 752 | CLANG_WARN_INFINITE_RECURSION = YES; 753 | CLANG_WARN_INT_CONVERSION = YES; 754 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 755 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 756 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 757 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 758 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 759 | CLANG_WARN_STRICT_PROTOTYPES = YES; 760 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 761 | CLANG_WARN_UNREACHABLE_CODE = YES; 762 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 763 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 764 | COPY_PHASE_STRIP = NO; 765 | CURRENT_PROJECT_VERSION = 1; 766 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 767 | ENABLE_NS_ASSERTIONS = NO; 768 | ENABLE_STRICT_OBJC_MSGSEND = YES; 769 | GCC_C_LANGUAGE_STANDARD = gnu99; 770 | GCC_NO_COMMON_BLOCKS = YES; 771 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 772 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 773 | GCC_WARN_UNDECLARED_SELECTOR = YES; 774 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 775 | GCC_WARN_UNUSED_FUNCTION = YES; 776 | GCC_WARN_UNUSED_VARIABLE = YES; 777 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 778 | MTL_ENABLE_DEBUG_INFO = NO; 779 | SDKROOT = iphoneos; 780 | SWIFT_COMPILATION_MODE = wholemodule; 781 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 782 | SWIFT_VERSION = 4.0; 783 | TARGETED_DEVICE_FAMILY = "1,2"; 784 | VALIDATE_PRODUCT = YES; 785 | VERSIONING_SYSTEM = "apple-generic"; 786 | VERSION_INFO_PREFIX = ""; 787 | }; 788 | name = Release; 789 | }; 790 | C99EAF541B90B05800D61E1B /* Debug */ = { 791 | isa = XCBuildConfiguration; 792 | buildSettings = { 793 | CLANG_ENABLE_MODULES = YES; 794 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 795 | DEFINES_MODULE = YES; 796 | DYLIB_COMPATIBILITY_VERSION = 1; 797 | DYLIB_CURRENT_VERSION = 1; 798 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 799 | INFOPLIST_FILE = PasscodeLock/Info.plist; 800 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 801 | LD_RUNPATH_SEARCH_PATHS = ( 802 | "$(inherited)", 803 | "@executable_path/Frameworks", 804 | "@loader_path/Frameworks", 805 | ); 806 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLock; 807 | PRODUCT_NAME = "$(TARGET_NAME)"; 808 | SKIP_INSTALL = YES; 809 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 810 | SWIFT_VERSION = 5.0; 811 | }; 812 | name = Debug; 813 | }; 814 | C99EAF551B90B05800D61E1B /* Release */ = { 815 | isa = XCBuildConfiguration; 816 | buildSettings = { 817 | CLANG_ENABLE_MODULES = YES; 818 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 819 | DEFINES_MODULE = YES; 820 | DYLIB_COMPATIBILITY_VERSION = 1; 821 | DYLIB_CURRENT_VERSION = 1; 822 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 823 | INFOPLIST_FILE = PasscodeLock/Info.plist; 824 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 825 | LD_RUNPATH_SEARCH_PATHS = ( 826 | "$(inherited)", 827 | "@executable_path/Frameworks", 828 | "@loader_path/Frameworks", 829 | ); 830 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLock; 831 | PRODUCT_NAME = "$(TARGET_NAME)"; 832 | SKIP_INSTALL = YES; 833 | SWIFT_VERSION = 5.0; 834 | }; 835 | name = Release; 836 | }; 837 | C99EAF571B90B05800D61E1B /* Debug */ = { 838 | isa = XCBuildConfiguration; 839 | buildSettings = { 840 | CLANG_ENABLE_MODULES = YES; 841 | INFOPLIST_FILE = PasscodeLockTests/Info.plist; 842 | LD_RUNPATH_SEARCH_PATHS = ( 843 | "$(inherited)", 844 | "@executable_path/Frameworks", 845 | "@loader_path/Frameworks", 846 | ); 847 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockTests; 848 | PRODUCT_NAME = "$(TARGET_NAME)"; 849 | SWIFT_OBJC_BRIDGING_HEADER = "PasscodeLockTests/PasscodeLockTests-Bridging-Header.h"; 850 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 851 | }; 852 | name = Debug; 853 | }; 854 | C99EAF581B90B05800D61E1B /* Release */ = { 855 | isa = XCBuildConfiguration; 856 | buildSettings = { 857 | CLANG_ENABLE_MODULES = YES; 858 | INFOPLIST_FILE = PasscodeLockTests/Info.plist; 859 | LD_RUNPATH_SEARCH_PATHS = ( 860 | "$(inherited)", 861 | "@executable_path/Frameworks", 862 | "@loader_path/Frameworks", 863 | ); 864 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockTests; 865 | PRODUCT_NAME = "$(TARGET_NAME)"; 866 | SWIFT_OBJC_BRIDGING_HEADER = "PasscodeLockTests/PasscodeLockTests-Bridging-Header.h"; 867 | }; 868 | name = Release; 869 | }; 870 | C9D3DF361B91AD12008561EB /* Debug */ = { 871 | isa = XCBuildConfiguration; 872 | buildSettings = { 873 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 874 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 875 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 876 | DEVELOPMENT_TEAM = 6HPG35RJ6Y; 877 | INFOPLIST_FILE = PasscodeLockDemo/Info.plist; 878 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 879 | LD_RUNPATH_SEARCH_PATHS = ( 880 | "$(inherited)", 881 | "@executable_path/Frameworks", 882 | ); 883 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemo; 884 | PRODUCT_NAME = "$(TARGET_NAME)"; 885 | SWIFT_VERSION = 5.0; 886 | TARGETED_DEVICE_FAMILY = "1,2"; 887 | }; 888 | name = Debug; 889 | }; 890 | C9D3DF371B91AD12008561EB /* Release */ = { 891 | isa = XCBuildConfiguration; 892 | buildSettings = { 893 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 894 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 895 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 896 | DEVELOPMENT_TEAM = 6HPG35RJ6Y; 897 | INFOPLIST_FILE = PasscodeLockDemo/Info.plist; 898 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 899 | LD_RUNPATH_SEARCH_PATHS = ( 900 | "$(inherited)", 901 | "@executable_path/Frameworks", 902 | ); 903 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemo; 904 | PRODUCT_NAME = "$(TARGET_NAME)"; 905 | SWIFT_VERSION = 5.0; 906 | TARGETED_DEVICE_FAMILY = "1,2"; 907 | }; 908 | name = Release; 909 | }; 910 | C9D3DF391B91AD12008561EB /* Debug */ = { 911 | isa = XCBuildConfiguration; 912 | buildSettings = { 913 | BUNDLE_LOADER = "$(TEST_HOST)"; 914 | INFOPLIST_FILE = PasscodeLockDemoTests/Info.plist; 915 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 916 | LD_RUNPATH_SEARCH_PATHS = ( 917 | "$(inherited)", 918 | "@executable_path/Frameworks", 919 | "@loader_path/Frameworks", 920 | ); 921 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoTests; 922 | PRODUCT_NAME = "$(TARGET_NAME)"; 923 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PasscodeLockDemo.app/PasscodeLockDemo"; 924 | }; 925 | name = Debug; 926 | }; 927 | C9D3DF3A1B91AD12008561EB /* Release */ = { 928 | isa = XCBuildConfiguration; 929 | buildSettings = { 930 | BUNDLE_LOADER = "$(TEST_HOST)"; 931 | INFOPLIST_FILE = PasscodeLockDemoTests/Info.plist; 932 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 933 | LD_RUNPATH_SEARCH_PATHS = ( 934 | "$(inherited)", 935 | "@executable_path/Frameworks", 936 | "@loader_path/Frameworks", 937 | ); 938 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoTests; 939 | PRODUCT_NAME = "$(TARGET_NAME)"; 940 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PasscodeLockDemo.app/PasscodeLockDemo"; 941 | }; 942 | name = Release; 943 | }; 944 | C9D3DF3C1B91AD12008561EB /* Debug */ = { 945 | isa = XCBuildConfiguration; 946 | buildSettings = { 947 | INFOPLIST_FILE = PasscodeLockDemoUITests/Info.plist; 948 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 949 | LD_RUNPATH_SEARCH_PATHS = ( 950 | "$(inherited)", 951 | "@executable_path/Frameworks", 952 | "@loader_path/Frameworks", 953 | ); 954 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoUITests; 955 | PRODUCT_NAME = "$(TARGET_NAME)"; 956 | TEST_TARGET_NAME = PasscodeLockDemo; 957 | USES_XCTRUNNER = YES; 958 | }; 959 | name = Debug; 960 | }; 961 | C9D3DF3D1B91AD12008561EB /* Release */ = { 962 | isa = XCBuildConfiguration; 963 | buildSettings = { 964 | INFOPLIST_FILE = PasscodeLockDemoUITests/Info.plist; 965 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 966 | LD_RUNPATH_SEARCH_PATHS = ( 967 | "$(inherited)", 968 | "@executable_path/Frameworks", 969 | "@loader_path/Frameworks", 970 | ); 971 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoUITests; 972 | PRODUCT_NAME = "$(TARGET_NAME)"; 973 | TEST_TARGET_NAME = PasscodeLockDemo; 974 | USES_XCTRUNNER = YES; 975 | }; 976 | name = Release; 977 | }; 978 | /* End XCBuildConfiguration section */ 979 | 980 | /* Begin XCConfigurationList section */ 981 | C99EAF391B90B05700D61E1B /* Build configuration list for PBXProject "PasscodeLock" */ = { 982 | isa = XCConfigurationList; 983 | buildConfigurations = ( 984 | C99EAF511B90B05800D61E1B /* Debug */, 985 | C99EAF521B90B05800D61E1B /* Release */, 986 | ); 987 | defaultConfigurationIsVisible = 0; 988 | defaultConfigurationName = Release; 989 | }; 990 | C99EAF531B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLock" */ = { 991 | isa = XCConfigurationList; 992 | buildConfigurations = ( 993 | C99EAF541B90B05800D61E1B /* Debug */, 994 | C99EAF551B90B05800D61E1B /* Release */, 995 | ); 996 | defaultConfigurationIsVisible = 0; 997 | defaultConfigurationName = Release; 998 | }; 999 | C99EAF561B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLockTests" */ = { 1000 | isa = XCConfigurationList; 1001 | buildConfigurations = ( 1002 | C99EAF571B90B05800D61E1B /* Debug */, 1003 | C99EAF581B90B05800D61E1B /* Release */, 1004 | ); 1005 | defaultConfigurationIsVisible = 0; 1006 | defaultConfigurationName = Release; 1007 | }; 1008 | C9D3DF351B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemo" */ = { 1009 | isa = XCConfigurationList; 1010 | buildConfigurations = ( 1011 | C9D3DF361B91AD12008561EB /* Debug */, 1012 | C9D3DF371B91AD12008561EB /* Release */, 1013 | ); 1014 | defaultConfigurationIsVisible = 0; 1015 | defaultConfigurationName = Release; 1016 | }; 1017 | C9D3DF381B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoTests" */ = { 1018 | isa = XCConfigurationList; 1019 | buildConfigurations = ( 1020 | C9D3DF391B91AD12008561EB /* Debug */, 1021 | C9D3DF3A1B91AD12008561EB /* Release */, 1022 | ); 1023 | defaultConfigurationIsVisible = 0; 1024 | defaultConfigurationName = Release; 1025 | }; 1026 | C9D3DF3B1B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoUITests" */ = { 1027 | isa = XCConfigurationList; 1028 | buildConfigurations = ( 1029 | C9D3DF3C1B91AD12008561EB /* Debug */, 1030 | C9D3DF3D1B91AD12008561EB /* Release */, 1031 | ); 1032 | defaultConfigurationIsVisible = 0; 1033 | defaultConfigurationName = Release; 1034 | }; 1035 | /* End XCConfigurationList section */ 1036 | }; 1037 | rootObject = C99EAF361B90B05700D61E1B /* Project object */; 1038 | } 1039 | -------------------------------------------------------------------------------- /PasscodeLock.xcodeproj/xcshareddata/xcschemes/PasscodeLock.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /PasscodeLock/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func localizedStringFor(key: String, comment: String) -> String { 12 | 13 | let name = "PasscodeLock" 14 | let defaultString = NSLocalizedString(key, tableName: name, bundle: Bundle(for: PasscodeLock.self), comment: comment) 15 | 16 | return NSLocalizedString(key, tableName: name, bundle: Bundle.main, value: defaultString, comment: comment) 17 | } 18 | 19 | func bundleForResource(name: String, ofType type: String) -> Bundle { 20 | if Bundle.main.path(forResource: name, ofType: type) != nil { 21 | return .main 22 | } 23 | 24 | return Bundle(for: PasscodeLock.self) 25 | } 26 | -------------------------------------------------------------------------------- /PasscodeLock/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock.h: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLock.h 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PasscodeLock. 12 | FOUNDATION_EXPORT double PasscodeLockVersionNumber; 13 | 14 | //! Project version string for PasscodeLock. 15 | FOUNDATION_EXPORT const unsigned char PasscodeLockVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock/ChangePasscodeState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangePasscodeState.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ChangePasscodeState: PasscodeLockStateType { 12 | 13 | let title: String 14 | let description: String 15 | let isCancellableAction = true 16 | var isTouchIDAllowed = false 17 | 18 | init() { 19 | 20 | title = localizedStringFor(key: "PasscodeLockChangeTitle", comment: "Change passcode title") 21 | description = localizedStringFor(key: "PasscodeLockChangeDescription", comment: "Change passcode description") 22 | } 23 | 24 | func accept(passcode: String, from lock: PasscodeLockType) { 25 | 26 | if lock.repository.check(passcode: passcode) { 27 | 28 | lock.changeState(SetPasscodeState()) 29 | 30 | } else { 31 | 32 | lock.delegate?.passcodeLockDidFail(lock) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock/ConfirmPasscodeState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfirmPasscodeState.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ConfirmPasscodeState: PasscodeLockStateType { 12 | 13 | let title: String 14 | let description: String 15 | let isCancellableAction = true 16 | var isTouchIDAllowed = false 17 | 18 | private var passcodeToConfirm: String 19 | 20 | init(passcode: String) { 21 | 22 | passcodeToConfirm = passcode 23 | title = localizedStringFor(key: "PasscodeLockConfirmTitle", comment: "Confirm passcode title") 24 | description = localizedStringFor(key: "PasscodeLockConfirmDescription", comment: "Confirm passcode description") 25 | } 26 | 27 | func accept(passcode: String, from lock: PasscodeLockType) { 28 | 29 | if passcode == passcodeToConfirm { 30 | 31 | lock.repository.save(passcode: passcode) 32 | lock.delegate?.passcodeLockDidSucceed(lock) 33 | 34 | } else { 35 | 36 | let mismatchTitle = localizedStringFor(key: "PasscodeLockMismatchTitle", comment: "Passcode mismatch title") 37 | let mismatchDescription = localizedStringFor(key: "PasscodeLockMismatchDescription", comment: "Passcode mismatch description") 38 | 39 | lock.changeState(SetPasscodeState(title: mismatchTitle, description: mismatchDescription)) 40 | 41 | lock.delegate?.passcodeLockDidFail(lock) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock/EnterPasscodeState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnterPasscodeState.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public let PasscodeLockIncorrectPasscodeNotification = Notification.Name("passcode.lock.incorrect.passcode.notification") 12 | 13 | struct EnterPasscodeState: PasscodeLockStateType { 14 | let title: String 15 | let description: String 16 | let isCancellableAction: Bool 17 | var isTouchIDAllowed = true 18 | private var isNotificationSent = false 19 | 20 | init(allowCancellation: Bool = false) { 21 | isCancellableAction = allowCancellation 22 | title = localizedStringFor(key: "PasscodeLockEnterTitle", comment: "Enter passcode title") 23 | description = localizedStringFor(key: "PasscodeLockEnterDescription", comment: "Enter passcode description") 24 | } 25 | 26 | mutating func accept(passcode: String, from lock: PasscodeLockType) { 27 | if lock.repository.check(passcode: passcode) { 28 | lock.delegate?.passcodeLockDidSucceed(lock) 29 | lock.configuration.setIncorrectPasscodeAttempts(0) 30 | } else { 31 | let oldValue = lock.configuration.getIncorrectPasscodeAttempts() 32 | lock.configuration.setIncorrectPasscodeAttempts(oldValue + 1) 33 | 34 | if lock.configuration.getIncorrectPasscodeAttempts() >= lock.configuration.maximumIncorrectPasscodeAttempts { 35 | postNotification() 36 | } 37 | 38 | lock.delegate?.passcodeLockDidFail(lock) 39 | } 40 | } 41 | 42 | private mutating func postNotification() { 43 | guard !isNotificationSent else { return } 44 | NotificationCenter.default.post(name: PasscodeLockIncorrectPasscodeNotification, object: nil) 45 | isNotificationSent = true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock/PasscodeLock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLock.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LocalAuthentication 11 | 12 | open class PasscodeLock: PasscodeLockType { 13 | open weak var delegate: PasscodeLockTypeDelegate? 14 | public let configuration: PasscodeLockConfigurationType 15 | 16 | open var repository: PasscodeRepositoryType { 17 | return configuration.repository 18 | } 19 | 20 | open var state: PasscodeLockStateType { 21 | return lockState 22 | } 23 | 24 | open var isTouchIDAllowed: Bool { 25 | return isTouchIDEnabled() && configuration.isTouchIDAllowed && lockState.isTouchIDAllowed 26 | } 27 | 28 | private var lockState: PasscodeLockStateType 29 | private lazy var passcode = String() 30 | 31 | public init(state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType) { 32 | precondition(configuration.passcodeLength > 0, "Passcode length sould be greather than zero.") 33 | 34 | lockState = state 35 | self.configuration = configuration 36 | } 37 | 38 | open func addSign(_ sign: String) { 39 | passcode.append(sign) 40 | delegate?.passcodeLock(self, addedSignAt: passcode.count - 1) 41 | 42 | if passcode.count >= configuration.passcodeLength { 43 | lockState.accept(passcode: passcode, from: self) 44 | passcode.removeAll(keepingCapacity: true) 45 | } 46 | } 47 | 48 | open func removeSign() { 49 | guard passcode.count > 0 else { return } 50 | passcode.remove(at: passcode.index(before: passcode.endIndex)) 51 | delegate?.passcodeLock(self, removedSignAt: passcode.utf8.count) 52 | } 53 | 54 | open func changeState(_ state: PasscodeLockStateType) { 55 | DispatchQueue.main.async { [weak self] in 56 | guard let strongSelf = self else { return } 57 | strongSelf.lockState = state 58 | strongSelf.delegate?.passcodeLockDidChangeState(strongSelf) 59 | } 60 | } 61 | 62 | open func authenticateWithTouchID() { 63 | guard isTouchIDAllowed else { return } 64 | 65 | let context = LAContext() 66 | let reason = localizedStringFor(key: "PasscodeLockTouchIDReason", comment: "TouchID authentication reason") 67 | 68 | context.localizedFallbackTitle = localizedStringFor(key: "PasscodeLockTouchIDButton", comment: "TouchID authentication fallback button") 69 | 70 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { 71 | success, error in 72 | 73 | self.handleTouchIDResult(success) 74 | } 75 | } 76 | 77 | private func handleTouchIDResult(_ success: Bool) { 78 | DispatchQueue.main.async { [weak self] in 79 | guard success, let strongSelf = self else { return } 80 | strongSelf.delegate?.passcodeLockDidSucceed(strongSelf) 81 | } 82 | } 83 | 84 | private func isTouchIDEnabled() -> Bool { 85 | let context = LAContext() 86 | return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock/RemovePasscodeState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemovePasscodeState.swift 3 | // PasscodeLock 4 | // 5 | // Created by Kevin Seidel on 06/10/16. 6 | // Copyright © 2016 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct RemovePasscodeState: PasscodeLockStateType { 12 | let title: String 13 | let description: String 14 | let isCancellableAction = false 15 | var isTouchIDAllowed: Bool { return false } 16 | private var isNotificationSent = false 17 | 18 | init() { 19 | title = localizedStringFor(key: "PasscodeLockEnterTitle", comment: "Enter passcode title") 20 | description = localizedStringFor(key: "PasscodeLockEnterDescription", comment: "Enter passcode description") 21 | } 22 | 23 | mutating func accept(passcode: String, from lock: PasscodeLockType) { 24 | if lock.repository.check(passcode: passcode) { 25 | lock.repository.delete() 26 | lock.delegate?.passcodeLockDidSucceed(lock) 27 | lock.configuration.setIncorrectPasscodeAttempts(0) 28 | } else { 29 | let oldValue = lock.configuration.getIncorrectPasscodeAttempts() 30 | lock.configuration.setIncorrectPasscodeAttempts(oldValue + 1) 31 | 32 | if lock.configuration.getIncorrectPasscodeAttempts() >= lock.configuration.maximumIncorrectPasscodeAttempts { 33 | postNotification() 34 | } 35 | 36 | lock.delegate?.passcodeLockDidFail(lock) 37 | } 38 | } 39 | 40 | private mutating func postNotification() { 41 | guard !isNotificationSent else { return } 42 | NotificationCenter.default.post(name: PasscodeLockIncorrectPasscodeNotification, object: nil) 43 | isNotificationSent = true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLock/SetPasscodeState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetPasscodeState.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SetPasscodeState: PasscodeLockStateType { 12 | 13 | let title: String 14 | let description: String 15 | let isCancellableAction = true 16 | var isTouchIDAllowed = false 17 | 18 | init(title: String, description: String) { 19 | 20 | self.title = title 21 | self.description = description 22 | } 23 | 24 | init() { 25 | 26 | title = localizedStringFor(key: "PasscodeLockSetTitle", comment: "Set passcode title") 27 | description = localizedStringFor(key: "PasscodeLockSetDescription", comment: "Set passcode description") 28 | } 29 | 30 | func accept(passcode: String, from lock: PasscodeLockType) { 31 | 32 | lock.changeState(ConfirmPasscodeState(passcode: passcode)) 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLockPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockPresenter.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/29/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class PasscodeLockPresenter { 12 | 13 | private var mainWindow: UIWindow? 14 | 15 | private lazy var passcodeLockWindow: UIWindow = { 16 | let window = UIWindow(frame: UIScreen.main.bounds) 17 | window.accessibilityLabel = "PasscodeLock Window" 18 | return window 19 | }() 20 | 21 | private let passcodeConfiguration: PasscodeLockConfigurationType 22 | open var isPasscodePresented = false 23 | public let passcodeLockVC: PasscodeLockViewController 24 | 25 | public init(mainWindow window: UIWindow?, configuration: PasscodeLockConfigurationType, viewController: PasscodeLockViewController) { 26 | 27 | mainWindow = window 28 | passcodeConfiguration = configuration 29 | 30 | passcodeLockVC = viewController 31 | } 32 | 33 | public convenience init(mainWindow window: UIWindow?, configuration: PasscodeLockConfigurationType) { 34 | 35 | let passcodeLockVC = PasscodeLockViewController(state: .enter, configuration: configuration) 36 | 37 | self.init(mainWindow: window, configuration: configuration, viewController: passcodeLockVC) 38 | } 39 | 40 | open func present() { 41 | 42 | guard passcodeConfiguration.repository.hasPasscode else { return } 43 | guard !isPasscodePresented else { return } 44 | 45 | isPasscodePresented = true 46 | 47 | mainWindow?.endEditing(true) 48 | moveWindowsToFront() 49 | passcodeLockWindow.isHidden = false 50 | 51 | let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback 52 | 53 | passcodeLockVC.dismissCompletionCallback = { [weak self] in 54 | 55 | userDismissCompletionCallback?() 56 | 57 | self?.dismiss() 58 | } 59 | 60 | passcodeLockWindow.rootViewController = passcodeLockVC 61 | } 62 | 63 | open func dismiss(animated: Bool = true) { 64 | 65 | isPasscodePresented = false 66 | 67 | if animated { 68 | 69 | animatePasscodeLockDismissal() 70 | 71 | } else { 72 | passcodeLockWindow.isHidden = true 73 | passcodeLockWindow.rootViewController = nil 74 | } 75 | } 76 | 77 | internal func animatePasscodeLockDismissal() { 78 | UIView.animate( 79 | withDuration: 0.5, 80 | delay: 0, 81 | usingSpringWithDamping: 1, 82 | initialSpringVelocity: 0, 83 | options: [], 84 | animations: { [weak self] in 85 | self?.passcodeLockWindow.alpha = 0 86 | }, 87 | completion: { [weak self] _ in 88 | self?.passcodeLockWindow.isHidden = true 89 | self?.passcodeLockWindow.rootViewController = nil 90 | self?.passcodeLockWindow.alpha = 1 91 | } 92 | ) 93 | } 94 | 95 | private func moveWindowsToFront() { 96 | let windowLevel = UIApplication.shared.windows.last?.windowLevel ?? UIWindow.Level.normal 97 | let maxWinLevel = max(windowLevel, .normal) 98 | passcodeLockWindow.windowLevel = maxWinLevel + 1 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /PasscodeLock/PasscodeLockViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockViewController.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class PasscodeLockViewController: UIViewController, PasscodeLockTypeDelegate { 12 | public enum LockState { 13 | case enter 14 | case set 15 | case change 16 | case remove 17 | 18 | func getState() -> PasscodeLockStateType { 19 | switch self { 20 | case .enter: return EnterPasscodeState() 21 | case .set: return SetPasscodeState() 22 | case .change: return ChangePasscodeState() 23 | case .remove: return RemovePasscodeState() 24 | } 25 | } 26 | } 27 | 28 | private static var nibName: String { return "PasscodeLockView" } 29 | 30 | open class var nibBundle: Bundle { 31 | return bundleForResource(name: nibName, ofType: "nib") 32 | } 33 | 34 | @IBOutlet open var placeholders: [PasscodeSignPlaceholderView] = [PasscodeSignPlaceholderView]() 35 | @IBOutlet open weak var titleLabel: UILabel? 36 | @IBOutlet open weak var descriptionLabel: UILabel? 37 | @IBOutlet open weak var cancelButton: UIButton? 38 | @IBOutlet open weak var deleteSignButton: UIButton? 39 | @IBOutlet open weak var touchIDButton: UIButton? 40 | @IBOutlet open weak var placeholdersX: NSLayoutConstraint? 41 | 42 | open var successCallback: ((_ lock: PasscodeLockType) -> Void)? 43 | open var dismissCompletionCallback: (() -> Void)? 44 | open var animateOnDismiss: Bool 45 | open var notificationCenter: NotificationCenter? 46 | 47 | internal let passcodeConfiguration: PasscodeLockConfigurationType 48 | internal var passcodeLock: PasscodeLockType 49 | internal var isPlaceholdersAnimationCompleted = true 50 | 51 | private var shouldTryToAuthenticateWithBiometrics = true 52 | 53 | // MARK: - Initializers 54 | 55 | public init(state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType, animateOnDismiss: Bool = true) { 56 | self.animateOnDismiss = animateOnDismiss 57 | 58 | passcodeConfiguration = configuration 59 | passcodeLock = PasscodeLock(state: state, configuration: configuration) 60 | 61 | let this = type(of: self) 62 | super.init(nibName: this.nibName, bundle: this.nibBundle) 63 | 64 | passcodeLock.delegate = self 65 | notificationCenter = NotificationCenter.default 66 | } 67 | 68 | public convenience init(state: LockState, configuration: PasscodeLockConfigurationType, animateOnDismiss: Bool = true) { 69 | self.init(state: state.getState(), configuration: configuration, animateOnDismiss: animateOnDismiss) 70 | } 71 | 72 | public required init(coder aDecoder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | 76 | deinit { 77 | clearEvents() 78 | } 79 | 80 | // MARK: - View 81 | 82 | open override func viewDidLoad() { 83 | super.viewDidLoad() 84 | 85 | updatePasscodeView() 86 | deleteSignButton?.isEnabled = false 87 | 88 | cancelButton?.setTitle(localizedStringFor(key: "PasscodeLockCancelButtonTitle", comment: "Cancel Button Title"), for: .normal) 89 | deleteSignButton?.setTitle(localizedStringFor(key: "PasscodeLockDeleteButtonTitle", comment: "Delete Button Title"), for: .normal) 90 | 91 | setupEvents() 92 | } 93 | 94 | open override func viewDidAppear(_ animated: Bool) { 95 | super.viewDidAppear(animated) 96 | 97 | if shouldTryToAuthenticateWithBiometrics { 98 | authenticateWithTouchID() 99 | } 100 | } 101 | 102 | internal func updatePasscodeView() { 103 | titleLabel?.text = passcodeLock.state.title 104 | descriptionLabel?.text = passcodeLock.state.description 105 | cancelButton?.isHidden = !passcodeLock.state.isCancellableAction 106 | touchIDButton?.isHidden = !passcodeLock.isTouchIDAllowed 107 | } 108 | 109 | // MARK: - Events 110 | 111 | private func setupEvents() { 112 | notificationCenter?.addObserver(self, selector: #selector(PasscodeLockViewController.appWillEnterForegroundHandler(_:)), name: UIApplication.willEnterForegroundNotification, object: nil) 113 | notificationCenter?.addObserver(self, selector: #selector(PasscodeLockViewController.appDidEnterBackgroundHandler(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil) 114 | } 115 | 116 | private func clearEvents() { 117 | notificationCenter?.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) 118 | notificationCenter?.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) 119 | } 120 | 121 | @objc open func appWillEnterForegroundHandler(_ notification: Notification) { 122 | authenticateWithTouchID() 123 | } 124 | 125 | @objc open func appDidEnterBackgroundHandler(_ notification: Notification) { 126 | shouldTryToAuthenticateWithBiometrics = false 127 | } 128 | 129 | // MARK: - Actions 130 | 131 | @IBAction func passcodeSignButtonTap(_ sender: PasscodeSignButton) { 132 | guard isPlaceholdersAnimationCompleted else { return } 133 | 134 | passcodeLock.addSign(sender.passcodeSign) 135 | } 136 | 137 | @IBAction func cancelButtonTap(_ sender: UIButton) { 138 | dismissPasscodeLock(passcodeLock) 139 | } 140 | 141 | @IBAction func deleteSignButtonTap(_ sender: UIButton) { 142 | passcodeLock.removeSign() 143 | } 144 | 145 | @IBAction func touchIDButtonTap(_ sender: UIButton) { 146 | passcodeLock.authenticateWithTouchID() 147 | } 148 | 149 | private func authenticateWithTouchID() { 150 | if passcodeConfiguration.shouldRequestTouchIDImmediately && passcodeLock.isTouchIDAllowed { 151 | passcodeLock.authenticateWithTouchID() 152 | } 153 | } 154 | 155 | internal func dismissPasscodeLock(_ lock: PasscodeLockType, completionHandler: (() -> Void)? = nil) { 156 | // if presented as modal 157 | if presentingViewController?.presentedViewController == self { 158 | dismiss(animated: animateOnDismiss) { [weak self] in 159 | self?.dismissCompletionCallback?() 160 | completionHandler?() 161 | } 162 | } else { 163 | // if pushed in a navigation controller 164 | _ = navigationController?.popViewController(animated: animateOnDismiss) 165 | dismissCompletionCallback?() 166 | completionHandler?() 167 | } 168 | } 169 | 170 | // MARK: - Animations 171 | 172 | internal func animateWrongPassword() { 173 | deleteSignButton?.isEnabled = false 174 | isPlaceholdersAnimationCompleted = false 175 | 176 | animatePlaceholders(placeholders, toState: .error) 177 | 178 | placeholdersX?.constant = -40 179 | view.layoutIfNeeded() 180 | 181 | UIView.animate( 182 | withDuration: 0.5, 183 | delay: 0, 184 | usingSpringWithDamping: 0.2, 185 | initialSpringVelocity: 0, 186 | options: [], 187 | animations: { 188 | self.placeholdersX?.constant = 0 189 | self.view.layoutIfNeeded() 190 | }, 191 | completion: { completed in 192 | self.isPlaceholdersAnimationCompleted = true 193 | self.animatePlaceholders(self.placeholders, toState: .inactive) 194 | } 195 | ) 196 | } 197 | 198 | internal func animatePlaceholders(_ placeholders: [PasscodeSignPlaceholderView], toState state: PasscodeSignPlaceholderView.State) { 199 | placeholders.forEach { $0.animateState(state) } 200 | } 201 | 202 | private func animatePlacehodlerAtIndex(_ index: Int, toState state: PasscodeSignPlaceholderView.State) { 203 | guard index < placeholders.count && index >= 0 else { return } 204 | 205 | placeholders[index].animateState(state) 206 | } 207 | 208 | // MARK: - PasscodeLockDelegate 209 | 210 | open func passcodeLockDidSucceed(_ lock: PasscodeLockType) { 211 | deleteSignButton?.isEnabled = true 212 | animatePlaceholders(placeholders, toState: .inactive) 213 | 214 | dismissPasscodeLock(lock) { [weak self] in 215 | self?.successCallback?(lock) 216 | } 217 | } 218 | 219 | open func passcodeLockDidFail(_ lock: PasscodeLockType) { 220 | animateWrongPassword() 221 | } 222 | 223 | open func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 224 | updatePasscodeView() 225 | animatePlaceholders(placeholders, toState: .inactive) 226 | deleteSignButton?.isEnabled = false 227 | } 228 | 229 | open func passcodeLock(_ lock: PasscodeLockType, addedSignAt index: Int) { 230 | animatePlacehodlerAtIndex(index, toState: .active) 231 | deleteSignButton?.isEnabled = true 232 | } 233 | 234 | open func passcodeLock(_ lock: PasscodeLockType, removedSignAt index: Int) { 235 | animatePlacehodlerAtIndex(index, toState: .inactive) 236 | 237 | if index == 0 { 238 | deleteSignButton?.isEnabled = false 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /PasscodeLock/Protocols/PasscodeLockConfigurationType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockConfigurationType.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol PasscodeLockConfigurationType { 12 | var repository: PasscodeRepositoryType { get } 13 | var passcodeLength: Int { get } 14 | var isTouchIDAllowed: Bool { get set } 15 | var shouldRequestTouchIDImmediately: Bool { get } 16 | var maximumIncorrectPasscodeAttempts: Int { get } 17 | func getIncorrectPasscodeAttempts() -> Int 18 | func setIncorrectPasscodeAttempts(_ value: Int) 19 | } 20 | 21 | private let incorrectPasscodeAttemptsKey = "incorrectPasscodeAttempts" 22 | 23 | extension PasscodeLockConfigurationType { 24 | public func getIncorrectPasscodeAttempts() -> Int { 25 | return UserDefaults.standard.integer(forKey: incorrectPasscodeAttemptsKey) 26 | } 27 | 28 | public func setIncorrectPasscodeAttempts(_ value: Int) { 29 | UserDefaults.standard.set(value, forKey: incorrectPasscodeAttemptsKey) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PasscodeLock/Protocols/PasscodeLockStateType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockStateType.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol PasscodeLockStateType { 12 | 13 | var title: String { get } 14 | var description: String { get } 15 | var isCancellableAction: Bool { get } 16 | var isTouchIDAllowed: Bool { get } 17 | 18 | mutating func accept(passcode: String, from lock: PasscodeLockType) 19 | } 20 | -------------------------------------------------------------------------------- /PasscodeLock/Protocols/PasscodeLockType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockType.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol PasscodeLockType { 12 | var delegate: PasscodeLockTypeDelegate? { get set } 13 | var configuration: PasscodeLockConfigurationType { get } 14 | var repository: PasscodeRepositoryType { get } 15 | var state: PasscodeLockStateType { get } 16 | var isTouchIDAllowed: Bool { get } 17 | 18 | func addSign(_ sign: String) 19 | func removeSign() 20 | func changeState(_ state: PasscodeLockStateType) 21 | func authenticateWithTouchID() 22 | } 23 | 24 | public protocol PasscodeLockTypeDelegate: class { 25 | func passcodeLockDidSucceed(_ lock: PasscodeLockType) 26 | func passcodeLockDidFail(_ lock: PasscodeLockType) 27 | func passcodeLockDidChangeState(_ lock: PasscodeLockType) 28 | func passcodeLock(_ lock: PasscodeLockType, addedSignAt index: Int) 29 | func passcodeLock(_ lock: PasscodeLockType, removedSignAt index: Int) 30 | } 31 | -------------------------------------------------------------------------------- /PasscodeLock/Protocols/PasscodeRepositoryType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeRepositoryType.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol PasscodeRepositoryType { 12 | 13 | var hasPasscode: Bool { get } 14 | 15 | func save(passcode: String) 16 | func check(passcode: String) -> Bool 17 | func delete() 18 | } 19 | -------------------------------------------------------------------------------- /PasscodeLock/Views/PasscodeLockView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 147 | 172 | 197 | 222 | 247 | 272 | 297 | 322 | 347 | 372 | 382 | 395 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | -------------------------------------------------------------------------------- /PasscodeLock/Views/PasscodeSignButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeSignButton.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | open class PasscodeSignButton: UIButton { 13 | @IBInspectable 14 | open var passcodeSign: String = "1" 15 | 16 | @IBInspectable 17 | open var borderColor: UIColor = .white { 18 | didSet { 19 | setupView() 20 | } 21 | } 22 | 23 | @IBInspectable 24 | open var borderRadius: CGFloat = 30 { 25 | didSet { 26 | setupView() 27 | } 28 | } 29 | 30 | @IBInspectable 31 | open var highlightBackgroundColor: UIColor = .clear { 32 | didSet { 33 | setupView() 34 | } 35 | } 36 | 37 | public override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | 40 | setupView() 41 | setupActions() 42 | } 43 | 44 | public required init?(coder aDecoder: NSCoder) { 45 | super.init(coder: aDecoder) 46 | 47 | setupActions() 48 | } 49 | 50 | open override var intrinsicContentSize: CGSize { 51 | return CGSize(width: 60, height: 60) 52 | } 53 | 54 | private var defaultBackgroundColor: UIColor = .clear 55 | 56 | private func setupView() { 57 | layer.borderWidth = 1 58 | layer.cornerRadius = borderRadius 59 | layer.borderColor = borderColor.cgColor 60 | 61 | if let backgroundColor = backgroundColor { 62 | defaultBackgroundColor = backgroundColor 63 | } 64 | } 65 | 66 | private func setupActions() { 67 | addTarget(self, action: #selector(PasscodeSignButton.handleTouchDown), for: .touchDown) 68 | addTarget(self, action: #selector(PasscodeSignButton.handleTouchUp), for: [.touchUpInside, .touchDragOutside, .touchCancel]) 69 | } 70 | 71 | @objc func handleTouchDown() { 72 | animateBackgroundColor(highlightBackgroundColor) 73 | } 74 | 75 | @objc func handleTouchUp() { 76 | animateBackgroundColor(defaultBackgroundColor) 77 | } 78 | 79 | private func animateBackgroundColor(_ color: UIColor) { 80 | UIView.animate( 81 | withDuration: 0.3, 82 | delay: 0.0, 83 | usingSpringWithDamping: 1, 84 | initialSpringVelocity: 0.0, 85 | options: [.allowUserInteraction, .beginFromCurrentState], 86 | animations: { 87 | self.backgroundColor = color 88 | }, 89 | completion: nil 90 | ) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /PasscodeLock/Views/PasscodeSignPlaceholderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeSignPlaceholderView.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | open class PasscodeSignPlaceholderView: UIView { 13 | 14 | public enum State { 15 | case inactive 16 | case active 17 | case error 18 | } 19 | 20 | @IBInspectable 21 | open var cornerRadius: CGFloat = 8 { 22 | didSet { 23 | setupView() 24 | } 25 | } 26 | 27 | @IBInspectable 28 | open var inactiveColor: UIColor = UIColor.white { 29 | didSet { 30 | setupView() 31 | } 32 | } 33 | 34 | @IBInspectable 35 | open var activeColor: UIColor = UIColor.gray { 36 | didSet { 37 | setupView() 38 | } 39 | } 40 | 41 | @IBInspectable 42 | open var errorColor: UIColor = UIColor.red { 43 | didSet { 44 | setupView() 45 | } 46 | } 47 | 48 | public override init(frame: CGRect) { 49 | 50 | super.init(frame: frame) 51 | 52 | setupView() 53 | } 54 | 55 | public required init?(coder aDecoder: NSCoder) { 56 | 57 | super.init(coder: aDecoder) 58 | } 59 | 60 | open override var intrinsicContentSize : CGSize { 61 | 62 | return CGSize(width: 16, height: 16) 63 | } 64 | 65 | private func setupView() { 66 | 67 | layer.cornerRadius = cornerRadius 68 | layer.borderWidth = 1 69 | layer.borderColor = activeColor.cgColor 70 | backgroundColor = inactiveColor 71 | } 72 | 73 | private func colorsForState(_ state: State) -> (backgroundColor: UIColor, borderColor: UIColor) { 74 | 75 | switch state { 76 | case .inactive: return (inactiveColor, activeColor) 77 | case .active: return (activeColor, activeColor) 78 | case .error: return (errorColor, errorColor) 79 | } 80 | } 81 | 82 | open func animateState(_ state: State) { 83 | 84 | let colors = colorsForState(state) 85 | 86 | UIView.animate( 87 | withDuration: 0.5, 88 | delay: 0, 89 | usingSpringWithDamping: 1, 90 | initialSpringVelocity: 0, 91 | options: [], 92 | animations: { 93 | 94 | self.backgroundColor = colors.backgroundColor 95 | self.layer.borderColor = colors.borderColor.cgColor 96 | 97 | }, 98 | completion: nil 99 | ) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PasscodeLock/en.lproj/PasscodeLock.strings: -------------------------------------------------------------------------------- 1 | /* 2 | PasscodeLock.strings 3 | PasscodeLock 4 | 5 | Created by Yanko Dimitrov on 8/28/15. 6 | Copyright (c) 2015 Yanko Dimitrov. All rights reserved. 7 | */ 8 | 9 | /* Enter Passcode State */ 10 | "PasscodeLockEnterTitle" = "Enter Passcode"; 11 | "PasscodeLockEnterDescription" = "Enter your passcode to proceed."; 12 | 13 | /* Set Passcode State */ 14 | "PasscodeLockSetTitle" = "Enter a Passcode"; 15 | "PasscodeLockSetDescription" = "Choose your passcode."; 16 | 17 | /* Confirm Passcode State */ 18 | "PasscodeLockConfirmTitle" = "Confirm Passcode"; 19 | "PasscodeLockConfirmDescription" = "Enter the passcode again."; 20 | 21 | /* Change Passcode State */ 22 | "PasscodeLockChangeTitle" = "Enter Old Passcode"; 23 | "PasscodeLockChangeDescription" = "Enter your old passcode."; 24 | 25 | /* Passcode Mismatch State */ 26 | "PasscodeLockMismatchTitle" = "Try again"; 27 | "PasscodeLockMismatchDescription" = "Passcodes didn\'t match."; 28 | 29 | /* Cancel Button Title */ 30 | "PasscodeLockCancelButtonTitle" = "Cancel"; 31 | 32 | /* Delete Button Title */ 33 | "PasscodeLockDeleteButtonTitle" = "Delete"; 34 | 35 | /* Touch ID Reason */ 36 | "PasscodeLockTouchIDReason" = "Authentication required to proceed"; 37 | 38 | /* Touch ID Fallback Button */ 39 | "PasscodeLockTouchIDButton" = "Enter Passcode"; 40 | -------------------------------------------------------------------------------- /PasscodeLock/ru.lproj/PasscodeLock.strings: -------------------------------------------------------------------------------- 1 | /* 2 | PasscodeLock.strings 3 | PasscodeLock 4 | 5 | Перевёл Ярослав Ерохин github/ysoftware 6 | Created by Yanko Dimitrov on 8/28/15. 7 | Copyright (c) 2015 Yanko Dimitrov. All rights reserved. 8 | */ 9 | 10 | /* Enter Passcode State */ 11 | "PasscodeLockEnterTitle" = "Введите пароль"; 12 | "PasscodeLockEnterDescription" = "Введите пароль, чтобы войти."; 13 | 14 | /* Set Passcode State */ 15 | "PasscodeLockSetTitle" = "Введите пароль"; 16 | "PasscodeLockSetDescription" = "Введите новый пароль."; 17 | 18 | /* Confirm Passcode State */ 19 | "PasscodeLockConfirmTitle" = "Подтвердите пароль"; 20 | "PasscodeLockConfirmDescription" = "Введите пароль ещё раз."; 21 | 22 | /* Change Passcode State */ 23 | "PasscodeLockChangeTitle" = "Введите старый пароль."; 24 | "PasscodeLockChangeDescription" = "Введите ваш старый пароль."; 25 | 26 | /* Passcode Mismatch State */ 27 | "PasscodeLockMismatchTitle" = "Попробуйте ещё раз."; 28 | "PasscodeLockMismatchDescription" = "Пароли должны совпадать."; 29 | 30 | /* Cancel Button Title */ 31 | "PasscodeLockCancelButtonTitle" = "Отмена"; 32 | 33 | /* Delete Button Title */ 34 | "PasscodeLockDeleteButtonTitle" = "Удалить"; 35 | 36 | /* Touch ID Reason */ 37 | "PasscodeLockTouchIDReason" = "Необходимо подтвердить личность."; 38 | 39 | /* Touch ID Fallback Button */ 40 | "PasscodeLockTouchIDButton" = "Ввести пароль."; 41 | -------------------------------------------------------------------------------- /PasscodeLockDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PasscodeLockDemo 4 | // 5 | // Created by Yanko Dimitrov on 8/29/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PasscodeLock 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | lazy var passcodeLockPresenter: PasscodeLockPresenter = { 18 | 19 | let configuration = PasscodeLockConfiguration() 20 | let presenter = PasscodeLockPresenter(mainWindow: self.window, configuration: configuration) 21 | 22 | return presenter 23 | }() 24 | 25 | func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 26 | 27 | passcodeLockPresenter.present() 28 | 29 | return true 30 | } 31 | 32 | 33 | func applicationWillResignActive(_ application: UIApplication) { 34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 35 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 36 | } 37 | 38 | func applicationDidEnterBackground(_ application: UIApplication) { 39 | passcodeLockPresenter.present() 40 | } 41 | 42 | func applicationWillEnterForeground(_ application: UIApplication) { 43 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 44 | } 45 | 46 | func applicationDidBecomeActive(_ application: UIApplication) { 47 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 48 | } 49 | 50 | func applicationWillTerminate(_ application: UIApplication) { 51 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /PasscodeLockDemo/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 | } -------------------------------------------------------------------------------- /PasscodeLockDemo/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 | 31 | 32 | -------------------------------------------------------------------------------- /PasscodeLockDemo/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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 43 | 50 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /PasscodeLockDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PasscodeLockDemo/PasscodeLockConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockConfiguration.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/29/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PasscodeLock 11 | 12 | struct PasscodeLockConfiguration: PasscodeLockConfigurationType { 13 | let repository: PasscodeRepositoryType 14 | let passcodeLength = 6 15 | var isTouchIDAllowed = true 16 | let shouldRequestTouchIDImmediately = true 17 | let maximumIncorrectPasscodeAttempts = -1 18 | 19 | init(repository: PasscodeRepositoryType) { 20 | self.repository = repository 21 | } 22 | 23 | init() { 24 | self.repository = UserDefaultsPasscodeRepository() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PasscodeLockDemo/PasscodeSettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeSettingsViewController.swift 3 | // PasscodeLockDemo 4 | // 5 | // Created by Yanko Dimitrov on 8/29/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PasscodeLock 11 | 12 | class PasscodeSettingsViewController: UIViewController { 13 | @IBOutlet var passcodeSwitch: UISwitch! 14 | @IBOutlet var changePasscodeButton: UIButton! 15 | @IBOutlet var testTextField: UITextField! 16 | @IBOutlet var testActivityButton: UIButton! 17 | 18 | private let configuration: PasscodeLockConfigurationType 19 | 20 | init(configuration: PasscodeLockConfigurationType) { 21 | self.configuration = configuration 22 | 23 | super.init(nibName: nil, bundle: nil) 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | let repository = UserDefaultsPasscodeRepository() 28 | configuration = PasscodeLockConfiguration(repository: repository) 29 | 30 | super.init(coder: aDecoder) 31 | } 32 | 33 | // MARK: - View 34 | 35 | override func viewWillAppear(_ animated: Bool) { 36 | super.viewWillAppear(animated) 37 | 38 | updatePasscodeView() 39 | } 40 | 41 | func updatePasscodeView() { 42 | let hasPasscode = configuration.repository.hasPasscode 43 | 44 | changePasscodeButton.isHidden = !hasPasscode 45 | passcodeSwitch.isOn = hasPasscode 46 | } 47 | 48 | // MARK: - Actions 49 | 50 | @IBAction func passcodeSwitchValueChange(sender: UISwitch) { 51 | let passcodeVC: PasscodeLockViewController 52 | 53 | if passcodeSwitch.isOn { 54 | passcodeVC = PasscodeLockViewController(state: .set, configuration: configuration) 55 | 56 | } else { 57 | passcodeVC = PasscodeLockViewController(state: .remove, configuration: configuration) 58 | } 59 | 60 | present(passcodeVC, animated: true, completion: nil) 61 | } 62 | 63 | @IBAction func changePasscodeButtonTap(sender: UIButton) { 64 | let repo = UserDefaultsPasscodeRepository() 65 | let config = PasscodeLockConfiguration(repository: repo) 66 | 67 | let passcodeLock = PasscodeLockViewController(state: .change, configuration: config) 68 | 69 | present(passcodeLock, animated: true, completion: nil) 70 | } 71 | 72 | @IBAction func testAlertButtonTap(sender: UIButton) { 73 | let alertVC = UIAlertController(title: "Test", message: "", preferredStyle: .alert) 74 | 75 | alertVC.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 76 | 77 | present(alertVC, animated: true, completion: nil) 78 | } 79 | 80 | @IBAction func testActivityButtonTap(sender: UIButton) { 81 | let activityVC = UIActivityViewController(activityItems: ["Test"], applicationActivities: nil) 82 | 83 | activityVC.popoverPresentationController?.sourceView = testActivityButton 84 | activityVC.popoverPresentationController?.sourceRect = CGRect(x: 10, y: 20, width: 0, height: 0) 85 | 86 | present(activityVC, animated: true, completion: nil) 87 | } 88 | 89 | @IBAction func dismissKeyboard() { 90 | testTextField.resignFirstResponder() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /PasscodeLockDemo/UserDefaultsPasscodeRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaultsPasscodeRepository.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/29/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PasscodeLock 11 | 12 | public enum PasscodeError: Error { 13 | case noPasscode 14 | } 15 | 16 | class UserDefaultsPasscodeRepository: PasscodeRepositoryType { 17 | private let passcodeKey = "passcode.lock.passcode" 18 | 19 | private lazy var defaults: UserDefaults = { 20 | UserDefaults.standard 21 | }() 22 | 23 | var hasPasscode: Bool { 24 | if passcode != nil { 25 | return true 26 | } 27 | 28 | return false 29 | } 30 | 31 | private var passcode: String? { 32 | return defaults.value(forKey: passcodeKey) as? String ?? nil 33 | } 34 | 35 | func save(passcode: String) { 36 | defaults.set(passcode, forKey: passcodeKey) 37 | defaults.synchronize() 38 | } 39 | 40 | func check(passcode: String) -> Bool { 41 | return self.passcode == passcode 42 | } 43 | 44 | func delete() { 45 | defaults.removeObject(forKey: passcodeKey) 46 | defaults.synchronize() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PasscodeLockDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PasscodeLockDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PasscodeLockTests/Fakes/FakePasscodeLock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakePasscodeLock.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FakePasscodeLock: PasscodeLockType { 12 | 13 | weak var delegate: PasscodeLockTypeDelegate? 14 | let configuration: PasscodeLockConfigurationType 15 | var repository: PasscodeRepositoryType { return configuration.repository } 16 | var state: PasscodeLockStateType { return lockState } 17 | let isTouchIDAllowed = false 18 | var lockState: PasscodeLockStateType 19 | 20 | var changeStateCalled = false 21 | 22 | init(state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType) { 23 | 24 | self.lockState = state 25 | self.configuration = configuration 26 | } 27 | 28 | func addSign(_ sign: String) { 29 | 30 | } 31 | 32 | func removeSign() { 33 | 34 | } 35 | 36 | func changeState(_ state: PasscodeLockStateType) { 37 | 38 | lockState = state 39 | changeStateCalled = true 40 | delegate?.passcodeLockDidChangeState(self) 41 | } 42 | 43 | func authenticateWithTouchID() { 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PasscodeLockTests/Fakes/FakePasscodeLockConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakePasscodeLockConfiguration.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FakePasscodeLockConfiguration: PasscodeLockConfigurationType { 12 | let repository: PasscodeRepositoryType 13 | let passcodeLength = 4 14 | var isTouchIDAllowed = false 15 | let maximumIncorrectPasscodeAttempts = 3 16 | let shouldRequestTouchIDImmediately = false 17 | 18 | init(repository: PasscodeRepositoryType) { 19 | self.repository = repository 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PasscodeLockTests/Fakes/FakePasscodeLockDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakePasscodeLockDelegate.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FakePasscodeLockDelegate: PasscodeLockTypeDelegate { 12 | 13 | func passcodeLockDidSucceed(_ lock: PasscodeLockType) {} 14 | func passcodeLockDidFail(_ lock: PasscodeLockType) {} 15 | func passcodeLockDidChangeState(_ lock: PasscodeLockType) {} 16 | func passcodeLock(_ lock: PasscodeLockType, addedSignAt index: Int) {} 17 | func passcodeLock(_ lock: PasscodeLockType, removedSignAt index: Int) {} 18 | } 19 | -------------------------------------------------------------------------------- /PasscodeLockTests/Fakes/FakePasscodeRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakePasscodeRepository.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FakePasscodeRepository: PasscodeRepositoryType { 12 | 13 | var hasPasscode: Bool { return true } 14 | private var passcode: String? { return fakePasscode } 15 | 16 | var fakePasscode = "1234" //["1", "2", "3", "4"] 17 | 18 | var savePasscodeCalled = false 19 | var savedPasscode = String() 20 | 21 | func save(passcode: String) { 22 | 23 | savePasscodeCalled = true 24 | savedPasscode = passcode 25 | } 26 | 27 | func delete() { 28 | 29 | } 30 | 31 | func check(passcode: String) -> Bool { 32 | return passcode == fakePasscode 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PasscodeLockTests/Fakes/FakePasscodeState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakePasscodeState.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FakePasscodeState: PasscodeLockStateType { 12 | 13 | var title = "A" 14 | var description = "B" 15 | var isCancellableAction = true 16 | var isTouchIDAllowed = true 17 | 18 | var acceptPaccodeCalled = false 19 | var acceptedPasscode = String() 20 | var numberOfAcceptedPasscodes = 0 21 | 22 | init() {} 23 | 24 | func accept(passcode: String, from lock: PasscodeLockType) { 25 | 26 | acceptedPasscode = passcode 27 | acceptPaccodeCalled = true 28 | numberOfAcceptedPasscodes += 1 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /PasscodeLockTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PasscodeLockTests/PasscodeLock/ChangePasscodeStateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangePasscodeStateTests.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ChangePasscodeStateTests: XCTestCase { 12 | 13 | var passcodeLock: FakePasscodeLock! 14 | var passcodeState: ChangePasscodeState! 15 | var repository: FakePasscodeRepository! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | repository = FakePasscodeRepository() 21 | 22 | let config = FakePasscodeLockConfiguration(repository: repository) 23 | 24 | passcodeState = ChangePasscodeState() 25 | passcodeLock = FakePasscodeLock(state: passcodeState, configuration: config) 26 | } 27 | 28 | func testAcceptCorrectPasscode() { 29 | 30 | class MockDelegate: FakePasscodeLockDelegate { 31 | 32 | var didChangedState = false 33 | 34 | override func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 35 | 36 | didChangedState = true 37 | } 38 | } 39 | 40 | let delegate = MockDelegate() 41 | 42 | passcodeLock.delegate = delegate 43 | passcodeState.accept(passcode: repository.fakePasscode, from: passcodeLock) 44 | 45 | XCTAssert(passcodeLock.state is SetPasscodeState, "Should change the state to SetPasscodeState") 46 | XCTAssertEqual(delegate.didChangedState, true, "Should call the delegate when the passcode is correct") 47 | } 48 | 49 | func testAcceptIncorrectPasscode() { 50 | 51 | class MockDelegate: FakePasscodeLockDelegate { 52 | 53 | var called = false 54 | 55 | override func passcodeLockDidFail(_ lock: PasscodeLockType) { 56 | 57 | called = true 58 | } 59 | } 60 | 61 | let delegate = MockDelegate() 62 | 63 | passcodeLock.delegate = delegate 64 | passcodeState.accept(passcode: "0000", from: passcodeLock) 65 | 66 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is incorrect") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PasscodeLockTests/PasscodeLock/ConfirmPasscodeStateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfirmPasscodeStateTests.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ConfirmPasscodeStateTests: XCTestCase { 12 | 13 | let passcodeToConfirm = "0000" ///["0", "0", "0", "0"] 14 | var passcodeLock: FakePasscodeLock! 15 | var passcodeState: ConfirmPasscodeState! 16 | var repository: FakePasscodeRepository! 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | repository = FakePasscodeRepository() 22 | 23 | let config = FakePasscodeLockConfiguration(repository: repository) 24 | 25 | passcodeState = ConfirmPasscodeState(passcode: passcodeToConfirm) 26 | passcodeLock = FakePasscodeLock(state: passcodeState, configuration: config) 27 | } 28 | 29 | func testAcceptCorrectPasscode() { 30 | 31 | class MockDelegate: FakePasscodeLockDelegate { 32 | 33 | var called = false 34 | 35 | override func passcodeLockDidSucceed(_ lock: PasscodeLockType) { 36 | 37 | called = true 38 | } 39 | } 40 | 41 | let delegate = MockDelegate() 42 | 43 | passcodeLock.delegate = delegate 44 | passcodeState.accept(passcode: passcodeToConfirm, from: passcodeLock) 45 | 46 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is correct") 47 | } 48 | 49 | func testAcceptCorrectPasscodeWillSaveThePasscode() { 50 | 51 | passcodeState.accept(passcode: passcodeToConfirm, from: passcodeLock) 52 | 53 | XCTAssertEqual(repository.savePasscodeCalled, true, "Should call the repository to save the new passcode") 54 | XCTAssertEqual(repository.savedPasscode, passcodeToConfirm, "Should save the confirmed passcode") 55 | } 56 | 57 | func testAcceptIncorrectPasscode() { 58 | 59 | class MockDelegate: FakePasscodeLockDelegate { 60 | 61 | var didFailed = false 62 | var didChangedState = false 63 | 64 | override func passcodeLockDidFail(_ lock: PasscodeLockType) { 65 | 66 | didFailed = true 67 | } 68 | 69 | override func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 70 | 71 | didChangedState = true 72 | } 73 | } 74 | 75 | let delegate = MockDelegate() 76 | 77 | passcodeLock.delegate = delegate 78 | passcodeState.accept(passcode: "12", from: passcodeLock) 79 | 80 | XCTAssertEqual(passcodeLock.changeStateCalled, true, "Should change the state") 81 | XCTAssert(passcodeLock.state is SetPasscodeState, "Should change the state to SetPasscodeState") 82 | XCTAssertEqual(delegate.didFailed, true, "Should call the delegate when the passcode confirmation fails") 83 | XCTAssertEqual(delegate.didChangedState, true, "Should change the state") 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /PasscodeLockTests/PasscodeLock/EnterPasscodeStateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnterPasscodeStateTests.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class NotificaionObserver: NSObject { 12 | 13 | var called = false 14 | var callCounter = 0 15 | 16 | func observe(notification: Notification.Name) { 17 | 18 | let center = NotificationCenter.default 19 | center.addObserver(self, selector: #selector(self.handle), name: notification, object: nil) 20 | } 21 | 22 | @objc func handle(notification: Notification) { 23 | 24 | called = true 25 | callCounter += 1 26 | } 27 | } 28 | 29 | class EnterPasscodeStateTests: XCTestCase { 30 | 31 | var passcodeLock: FakePasscodeLock! 32 | var passcodeState: EnterPasscodeState! 33 | var repository: FakePasscodeRepository! 34 | 35 | override func setUp() { 36 | super.setUp() 37 | 38 | repository = FakePasscodeRepository() 39 | 40 | let config = FakePasscodeLockConfiguration(repository: repository) 41 | 42 | passcodeState = EnterPasscodeState() 43 | passcodeLock = FakePasscodeLock(state: passcodeState, configuration: config) 44 | } 45 | 46 | func testAcceptCorrectPasscode() { 47 | 48 | class MockDelegate: FakePasscodeLockDelegate { 49 | 50 | var called = false 51 | 52 | override func passcodeLockDidSucceed(_ lock: PasscodeLockType) { 53 | 54 | called = true 55 | } 56 | } 57 | 58 | let delegate = MockDelegate() 59 | 60 | passcodeLock.delegate = delegate 61 | passcodeState.accept(passcode: repository.fakePasscode, from: passcodeLock) 62 | 63 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is correct") 64 | } 65 | 66 | func testAcceptIncorrectPasscode() { 67 | 68 | class MockDelegate: FakePasscodeLockDelegate { 69 | 70 | var called = false 71 | 72 | override func passcodeLockDidFail(_ lock: PasscodeLockType) { 73 | 74 | called = true 75 | } 76 | } 77 | 78 | let delegate = MockDelegate() 79 | 80 | passcodeLock.delegate = delegate 81 | passcodeState.accept(passcode: "0000", from: passcodeLock) 82 | 83 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is incorrect") 84 | } 85 | 86 | func testIncorrectPasscodeNotification() { 87 | 88 | let observer = NotificaionObserver() 89 | 90 | observer.observe(notification: PasscodeLockIncorrectPasscodeNotification) 91 | 92 | passcodeState.accept(passcode: "0", from: passcodeLock) 93 | passcodeState.accept(passcode: "0", from: passcodeLock) 94 | passcodeState.accept(passcode: "0", from: passcodeLock) 95 | 96 | XCTAssertEqual(observer.called, true, "Should send a notificaiton when the maximum number of incorrect attempts is reached") 97 | } 98 | 99 | func testIncorrectPasscodeSendNotificationOnce() { 100 | 101 | let observer = NotificaionObserver() 102 | 103 | observer.observe(notification: PasscodeLockIncorrectPasscodeNotification) 104 | 105 | passcodeState.accept(passcode: "0", from: passcodeLock) 106 | passcodeState.accept(passcode: "0", from: passcodeLock) 107 | passcodeState.accept(passcode: "0", from: passcodeLock) 108 | 109 | passcodeState.accept(passcode: "0", from: passcodeLock) 110 | passcodeState.accept(passcode: "0", from: passcodeLock) 111 | passcodeState.accept(passcode: "0", from: passcodeLock) 112 | 113 | XCTAssertEqual(observer.callCounter, 1, "Should send the notification only once") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /PasscodeLockTests/PasscodeLock/PasscodeLockTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasscodeLockTests.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class PasscodeLockTests: XCTestCase { 12 | var passcodeLock: PasscodeLock! 13 | var initialState: FakePasscodeState! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | let repository = FakePasscodeRepository() 19 | let config = FakePasscodeLockConfiguration(repository: repository) 20 | 21 | initialState = FakePasscodeState() 22 | passcodeLock = PasscodeLock(state: initialState, configuration: config) 23 | } 24 | 25 | func testSetStateTo() { 26 | class MockDelegate: FakePasscodeLockDelegate { 27 | var called = false 28 | 29 | override func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 30 | called = true 31 | } 32 | } 33 | 34 | let delegate = MockDelegate() 35 | let nextState = FakePasscodeState() 36 | 37 | passcodeLock.delegate = delegate 38 | passcodeLock.changeState(nextState) 39 | 40 | XCTAssertEqual(delegate.called, true, "Should inform the delegate for state changes") 41 | } 42 | 43 | func testAddSign() { 44 | class MockDelegate: FakePasscodeLockDelegate { 45 | var called = false 46 | var signIndex = 0 47 | 48 | override func passcodeLock(_ lock: PasscodeLockType, addedSignAt index: Int) { 49 | called = true 50 | signIndex = index 51 | } 52 | } 53 | 54 | let delegate = MockDelegate() 55 | 56 | passcodeLock.delegate = delegate 57 | passcodeLock.addSign("1") 58 | 59 | XCTAssertEqual(delegate.called, true, "Should inform the delegate for added sign at index") 60 | XCTAssertEqual(delegate.signIndex, 0, "Should return the added sign index") 61 | 62 | passcodeLock.addSign("2") 63 | 64 | XCTAssertEqual(delegate.signIndex, 1, "Should return the added sign index") 65 | } 66 | 67 | func testRemoveSign() { 68 | class MockDelegate: FakePasscodeLockDelegate { 69 | var called = false 70 | var signIndex = 0 71 | 72 | override func passcodeLock(_ lock: PasscodeLockType, removedSignAt index: Int) { 73 | called = true 74 | signIndex = index 75 | } 76 | } 77 | 78 | let delegate = MockDelegate() 79 | 80 | passcodeLock.delegate = delegate 81 | passcodeLock.addSign("1") 82 | passcodeLock.addSign("2") 83 | 84 | passcodeLock.removeSign() 85 | 86 | XCTAssertEqual(delegate.called, true, "Should inform the delegate for removed sign at index") 87 | XCTAssertEqual(delegate.signIndex, 1, "Should return the removed sign index") 88 | 89 | passcodeLock.removeSign() 90 | XCTAssertEqual(delegate.signIndex, 0, "Should return the removed sign index") 91 | } 92 | 93 | func testCallStateToAcceptTheEnteredPasscode() { 94 | let passcode = "0123" 95 | 96 | passcode.map { String($0) }.forEach(passcodeLock.addSign) 97 | 98 | XCTAssertEqual(initialState.acceptPaccodeCalled, true, "When the passcode length is reached should call the current state to accept the entered passcode") 99 | XCTAssertEqual(initialState.acceptedPasscode, passcode, "Should return the entered passcode") 100 | XCTAssertEqual(initialState.numberOfAcceptedPasscodes, 1, "Should call the accept passcode only once") 101 | } 102 | 103 | func testResetSigns() { 104 | let passcodeOne = "0123" // ["0", "1", "2", "3"] 105 | let passcodeTwo = "9876" // ["9", "8", "7", "6"] 106 | 107 | passcodeOne.map { String($0) }.forEach(passcodeLock.addSign) 108 | 109 | passcodeTwo.map { String($0) }.forEach(passcodeLock.addSign) 110 | 111 | XCTAssertEqual(initialState.numberOfAcceptedPasscodes, 2, "Should call the accept passcode twice") 112 | XCTAssertEqual(initialState.acceptedPasscode, passcodeTwo, "Shpuld return the last entered passcode") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /PasscodeLockTests/PasscodeLock/SetPasscodeStateTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetPasscodeStateTests.swift 3 | // PasscodeLock 4 | // 5 | // Created by Yanko Dimitrov on 8/28/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SetPasscodeStateTests: XCTestCase { 12 | 13 | var passcodeLock: FakePasscodeLock! 14 | var passcodeState: SetPasscodeState! 15 | var repository: FakePasscodeRepository! 16 | 17 | override func setUp() { 18 | super.setUp() 19 | 20 | repository = FakePasscodeRepository() 21 | 22 | let config = FakePasscodeLockConfiguration(repository: repository) 23 | 24 | passcodeState = SetPasscodeState() 25 | passcodeLock = FakePasscodeLock(state: passcodeState, configuration: config) 26 | } 27 | 28 | func testAcceptCorrectPasscode() { 29 | 30 | class MockDelegate: FakePasscodeLockDelegate { 31 | 32 | var didChangedState = false 33 | 34 | override func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 35 | 36 | didChangedState = true 37 | } 38 | } 39 | 40 | let delegate = MockDelegate() 41 | 42 | passcodeLock.delegate = delegate 43 | passcodeState.accept(passcode: repository.fakePasscode, from: passcodeLock) 44 | 45 | XCTAssert(passcodeLock.state is ConfirmPasscodeState, "Should change the state to ConfirmPasscodeState") 46 | XCTAssertEqual(delegate.didChangedState, true, "Should inform the delegate for the state change") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PasscodeLockTests/PasscodeLockTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PasscodeLock 2 | [![Build Status](https://travis-ci.org/zahlz/SwiftPasscodeLock.svg?branch=master)](https://travis-ci.org/zahlz/SwiftPasscodeLock) 3 | [![Swift4.1](https://img.shields.io/badge/swift4.1-compatible-brightgreen.svg)](https://apple.com/ios) 4 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | 6 | A Swift implementation of passcode lock for iOS with TouchID authentication. 7 | 8 |
9 | 10 | ## Requirements 11 | * iOS 8.0+ 12 | * Xcode 9.0+ 13 | * Swift 4.1+ 14 | 15 | ## Installation 16 | 17 | #### Carthage 18 | 19 | Add the following line to your [Cartfile](https://github.com/carthage/carthage) 20 | ```swift 21 | github "zahlz/SwiftPasscodeLock" 22 | ``` 23 | ## Usage 24 | 25 | 1. Create an implementation of the `PasscodeRepositoryType` protocol. 26 | 27 | 2. Create an implementation of the `PasscodeLockConfigurationType` protocol and set your preferred passcode lock configuration options. If you set the `maximumInccorectPasscodeAttempts` to a number greather than zero when the user reaches that number of incorrect passcode attempts a notification with name `PasscodeLockIncorrectPasscodeNotification` will be posted on the default `NotificationCenter`. 28 | 29 | 3. Create an instance of the `PasscodeLockPresenter` class. Next inside your `UIApplicationDelegate` implementation call it to present the passcode in `didFinishLaunchingWithOptions` and `applicationDidEnterBackground` methods. The passcode lock will be presented only if your user has set a passcode. 30 | 31 | 4. Allow your users to set a passcode by presenting the `PasscodeLockViewController` in `.set` state: 32 | 33 | ```swift 34 | let configuration = ... // your implementation of the PasscodeLockConfigurationType protocol 35 | 36 | let passcodeVC = PasscodeLockViewController(state: .set, configuration: configuration) 37 | 38 | presentViewController(passcodeVC, animated: true, completion: nil) 39 | ``` 40 | 41 | You can present the `PasscodeLockViewController` in one of the four initial states using the `LockState` enumeration options: `.enter`, `.set`, `.change`, `.remove`. 42 | 43 | Also you can set the initial passcode lock state to your own implementation of the `PasscodeLockStateType` protocol. 44 | 45 | ## Customization 46 | 47 | #### Custom Design 48 | 49 | The PasscodeLock will look for `PasscodeLockView.xib` inside your app bundle and if it can't find it will load its default one, so if you want to have a custom design create a new `xib` with the name `PasscodeLockView` and set its owner to an instance of `PasscodeLockViewController` class and module to `PasscodeLock`. 50 | 51 | Then connect the `view` outlet to the view of your `xib` file and make sure to connect the remaining `IBOutlet`s and `IBAction`s. Also make sure to set module to `PasscodeLock` on all `PasscodeSignPlaceholderView` and `PasscodeSignButton` in the nib. 52 | 53 | PasscodeLock comes with two view components: `PasscodeSignPlaceholderView` and `PasscodeSignButton` that you can use to create your own custom designs. Both classes are `@IBDesignable` and `@IBInspectable`, so you can see their appearance and change their properties right inside the interface builder: 54 | 55 |
56 | 57 | #### Localization 58 | 59 | Take a look at `PasscodeLock/en.lproj/PasscodeLock.strings` for the localization keys. Here again the PasscodeLock will look for the `PasscodeLock.strings` file inside your app bundle and if it can't find it will use the default localization file. 60 | 61 | ## Demo App 62 | 63 | The demo app comes with a simple implementation of the `PasscodeRepositoryType` protocol that is using the **UserDefaults** to store an retrieve the passcode. In your real applications you will probably want to use the **Keychain API**. Keep in mind that the **Keychain** records will not be removed when your user deletes your app. 64 | -------------------------------------------------------------------------------- /passcode-lock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zahlz/SwiftPasscodeLock/e01554598fad2886df63492f4f84ccc577b4593d/passcode-lock.gif -------------------------------------------------------------------------------- /passcode-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zahlz/SwiftPasscodeLock/e01554598fad2886df63492f4f84ccc577b4593d/passcode-view.png --------------------------------------------------------------------------------