├── .gitignore ├── 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 │ └── 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 ├── PasscodeLockDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── fake-logo.imageset │ │ ├── Contents.json │ │ └── TouchID_App_icon.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CustomPasscodeLockPresenter.swift ├── Info.plist ├── LockSplashView.swift ├── 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 ├── identity-inspector.png ├── 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 | -------------------------------------------------------------------------------- /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 = '1.0.3' 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/velikanov/SwiftPasscodeLock' 7 | s.authors = { 'Yanko Dimitrov' => '', 'Chris Ziogas' => '', } 8 | s.source = { :git => 'https://github.com/velikanov/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 = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C99EAF431B90B05700D61E1B /* PasscodeLock.h in Headers */ = {isa = PBXBuildFile; fileRef = C99EAF421B90B05700D61E1B /* PasscodeLock.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | C99EAF4A1B90B05800D61E1B /* PasscodeLock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; }; 12 | C9D3DF0B1B919CE4008561EB /* PasscodeLockView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF0A1B919CE4008561EB /* PasscodeLockView.xib */; }; 13 | C9D3DF131B91AD11008561EB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF121B91AD11008561EB /* AppDelegate.swift */; }; 14 | C9D3DF151B91AD11008561EB /* PasscodeSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF141B91AD11008561EB /* PasscodeSettingsViewController.swift */; }; 15 | C9D3DF181B91AD11008561EB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF161B91AD11008561EB /* Main.storyboard */; }; 16 | C9D3DF1A1B91AD11008561EB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF191B91AD11008561EB /* Assets.xcassets */; }; 17 | C9D3DF1D1B91AD11008561EB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9D3DF1B1B91AD11008561EB /* LaunchScreen.storyboard */; }; 18 | C9D3DF3E1B91AD7A008561EB /* PasscodeLock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; }; 19 | C9D3DF3F1B91AD7A008561EB /* PasscodeLock.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | C9D3DF441B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF431B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift */; }; 21 | C9D3DF461B91BD0E008561EB /* PasscodeLockConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF451B91BD0E008561EB /* PasscodeLockConfiguration.swift */; }; 22 | C9D3DF481B91F099008561EB /* PasscodeLockPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D3DF471B91F099008561EB /* PasscodeLockPresenter.swift */; }; 23 | C9DC07D51B90B1F6007A4DD0 /* PasscodeRepositoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */; }; 24 | C9DC07D61B90B1F6007A4DD0 /* PasscodeRepositoryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */; }; 25 | C9DC07D81B90B261007A4DD0 /* PasscodeLockStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */; }; 26 | C9DC07D91B90B261007A4DD0 /* PasscodeLockStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */; }; 27 | C9DC07DB1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */; }; 28 | C9DC07DC1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */; }; 29 | C9DC07DE1B90BA06007A4DD0 /* PasscodeLockType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */; }; 30 | C9DC07DF1B90BA06007A4DD0 /* PasscodeLockType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */; }; 31 | C9DC07E11B90BBFD007A4DD0 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */; }; 32 | C9DC07E21B90BBFD007A4DD0 /* PasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */; }; 33 | C9DC07E51B90BCF9007A4DD0 /* PasscodeLockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E41B90BCF9007A4DD0 /* PasscodeLockTests.swift */; }; 34 | C9DC07E71B90C382007A4DD0 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9DC07E61B90C382007A4DD0 /* LocalAuthentication.framework */; }; 35 | C9DC07EA1B90C690007A4DD0 /* FakePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07E91B90C690007A4DD0 /* FakePasscodeState.swift */; }; 36 | C9DC07EC1B90C72A007A4DD0 /* FakePasscodeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07EB1B90C72A007A4DD0 /* FakePasscodeRepository.swift */; }; 37 | C9DC07EE1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07ED1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift */; }; 38 | C9DC07F01B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07EF1B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift */; }; 39 | C9DC07F21B90C9DE007A4DD0 /* EnterPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */; }; 40 | C9DC07F31B90C9DE007A4DD0 /* EnterPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */; }; 41 | C9DC07F81B90CF29007A4DD0 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F71B90CF29007A4DD0 /* Functions.swift */; }; 42 | C9DC07F91B90CF29007A4DD0 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07F71B90CF29007A4DD0 /* Functions.swift */; }; 43 | C9DC07FC1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */; }; 44 | C9DC07FD1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */ = {isa = PBXBuildFile; fileRef = C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */; }; 45 | C9DC07FF1B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */; }; 46 | C9DC08001B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */; }; 47 | C9DC08021B90D2BA007A4DD0 /* ConfirmPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */; }; 48 | C9DC08031B90D2BA007A4DD0 /* ConfirmPasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */; }; 49 | C9DC08051B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */; }; 50 | C9DC08061B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */; }; 51 | C9DC08081B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08071B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift */; }; 52 | C9DC080A1B90DB09007A4DD0 /* FakePasscodeLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08091B90DB09007A4DD0 /* FakePasscodeLock.swift */; }; 53 | C9DC080C1B90DC38007A4DD0 /* SetPasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC080B1B90DC38007A4DD0 /* SetPasscodeStateTests.swift */; }; 54 | C9DC080E1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC080D1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift */; }; 55 | C9DC08101B90DD91007A4DD0 /* ChangePasscodeStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC080F1B90DD91007A4DD0 /* ChangePasscodeStateTests.swift */; }; 56 | C9DC08121B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08111B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift */; }; 57 | C9DC08141B90DE50007A4DD0 /* PasscodeSignButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08131B90DE50007A4DD0 /* PasscodeSignButton.swift */; }; 58 | C9DC08161B90DF4E007A4DD0 /* PasscodeLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DC08151B90DF4E007A4DD0 /* PasscodeLockViewController.swift */; }; 59 | DE6F8E1C1C24BF7500D3EFCF /* LockSplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6F8E1B1C24BF7500D3EFCF /* LockSplashView.swift */; }; 60 | DE6F8E1E1C24C09400D3EFCF /* CustomPasscodeLockPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE6F8E1D1C24C09400D3EFCF /* CustomPasscodeLockPresenter.swift */; }; 61 | /* End PBXBuildFile section */ 62 | 63 | /* Begin PBXContainerItemProxy section */ 64 | C99EAF4B1B90B05800D61E1B /* PBXContainerItemProxy */ = { 65 | isa = PBXContainerItemProxy; 66 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 67 | proxyType = 1; 68 | remoteGlobalIDString = C99EAF3E1B90B05700D61E1B; 69 | remoteInfo = PasscodeLock; 70 | }; 71 | C9D3DF241B91AD12008561EB /* PBXContainerItemProxy */ = { 72 | isa = PBXContainerItemProxy; 73 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 74 | proxyType = 1; 75 | remoteGlobalIDString = C9D3DF0F1B91AD11008561EB; 76 | remoteInfo = PasscodeLockDemo; 77 | }; 78 | C9D3DF2F1B91AD12008561EB /* PBXContainerItemProxy */ = { 79 | isa = PBXContainerItemProxy; 80 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 81 | proxyType = 1; 82 | remoteGlobalIDString = C9D3DF0F1B91AD11008561EB; 83 | remoteInfo = PasscodeLockDemo; 84 | }; 85 | C9D3DF401B91AD7A008561EB /* PBXContainerItemProxy */ = { 86 | isa = PBXContainerItemProxy; 87 | containerPortal = C99EAF361B90B05700D61E1B /* Project object */; 88 | proxyType = 1; 89 | remoteGlobalIDString = C99EAF3E1B90B05700D61E1B; 90 | remoteInfo = PasscodeLock; 91 | }; 92 | /* End PBXContainerItemProxy section */ 93 | 94 | /* Begin PBXCopyFilesBuildPhase section */ 95 | C9D3DF421B91AD7A008561EB /* Embed Frameworks */ = { 96 | isa = PBXCopyFilesBuildPhase; 97 | buildActionMask = 2147483647; 98 | dstPath = ""; 99 | dstSubfolderSpec = 10; 100 | files = ( 101 | C9D3DF3F1B91AD7A008561EB /* PasscodeLock.framework in Embed Frameworks */, 102 | ); 103 | name = "Embed Frameworks"; 104 | runOnlyForDeploymentPostprocessing = 0; 105 | }; 106 | /* End PBXCopyFilesBuildPhase section */ 107 | 108 | /* Begin PBXFileReference section */ 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 | DE6F8E1B1C24BF7500D3EFCF /* LockSplashView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockSplashView.swift; sourceTree = ""; }; 156 | DE6F8E1D1C24C09400D3EFCF /* CustomPasscodeLockPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPasscodeLockPresenter.swift; sourceTree = ""; }; 157 | /* End PBXFileReference section */ 158 | 159 | /* Begin PBXFrameworksBuildPhase section */ 160 | C99EAF3B1B90B05700D61E1B /* Frameworks */ = { 161 | isa = PBXFrameworksBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | C9DC07E71B90C382007A4DD0 /* LocalAuthentication.framework in Frameworks */, 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | C99EAF461B90B05700D61E1B /* Frameworks */ = { 169 | isa = PBXFrameworksBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | C99EAF4A1B90B05800D61E1B /* PasscodeLock.framework in Frameworks */, 173 | ); 174 | runOnlyForDeploymentPostprocessing = 0; 175 | }; 176 | C9D3DF0D1B91AD11008561EB /* Frameworks */ = { 177 | isa = PBXFrameworksBuildPhase; 178 | buildActionMask = 2147483647; 179 | files = ( 180 | C9D3DF3E1B91AD7A008561EB /* PasscodeLock.framework in Frameworks */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | C9D3DF201B91AD11008561EB /* Frameworks */ = { 185 | isa = PBXFrameworksBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | C9D3DF2B1B91AD12008561EB /* Frameworks */ = { 192 | isa = PBXFrameworksBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXFrameworksBuildPhase section */ 199 | 200 | /* Begin PBXGroup section */ 201 | C99EAF351B90B05700D61E1B = { 202 | isa = PBXGroup; 203 | children = ( 204 | C99EAF411B90B05700D61E1B /* PasscodeLock */, 205 | C99EAF4D1B90B05800D61E1B /* PasscodeLockTests */, 206 | C9D3DF111B91AD11008561EB /* PasscodeLockDemo */, 207 | C9D3DF261B91AD12008561EB /* PasscodeLockDemoTests */, 208 | C9D3DF311B91AD12008561EB /* PasscodeLockDemoUITests */, 209 | C99EAF401B90B05700D61E1B /* Products */, 210 | ); 211 | sourceTree = ""; 212 | }; 213 | C99EAF401B90B05700D61E1B /* Products */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */, 217 | C99EAF491B90B05800D61E1B /* PasscodeLockTests.xctest */, 218 | C9D3DF101B91AD11008561EB /* PasscodeLockDemo.app */, 219 | C9D3DF231B91AD11008561EB /* PasscodeLockDemoTests.xctest */, 220 | C9D3DF2E1B91AD12008561EB /* PasscodeLockDemoUITests.xctest */, 221 | ); 222 | name = Products; 223 | sourceTree = ""; 224 | }; 225 | C99EAF411B90B05700D61E1B /* PasscodeLock */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | C9DC07D21B90B1CE007A4DD0 /* Protocols */, 229 | C9DC07D11B90B1CE007A4DD0 /* PasscodeLock */, 230 | C9DC07D31B90B1CE007A4DD0 /* Views */, 231 | C9DC07E61B90C382007A4DD0 /* LocalAuthentication.framework */, 232 | C99EAF421B90B05700D61E1B /* PasscodeLock.h */, 233 | C99EAF441B90B05700D61E1B /* Info.plist */, 234 | C9DC07F71B90CF29007A4DD0 /* Functions.swift */, 235 | C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */, 236 | C9DC08151B90DF4E007A4DD0 /* PasscodeLockViewController.swift */, 237 | C9D3DF471B91F099008561EB /* PasscodeLockPresenter.swift */, 238 | ); 239 | path = PasscodeLock; 240 | sourceTree = ""; 241 | }; 242 | C99EAF4D1B90B05800D61E1B /* PasscodeLockTests */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | C9DC07E81B90C673007A4DD0 /* Fakes */, 246 | C9DC07E31B90BCE7007A4DD0 /* PasscodeLock */, 247 | C99EAF501B90B05800D61E1B /* Info.plist */, 248 | C9DC07CE1B90B0AC007A4DD0 /* PasscodeLockTests-Bridging-Header.h */, 249 | ); 250 | path = PasscodeLockTests; 251 | sourceTree = ""; 252 | }; 253 | C9D3DF111B91AD11008561EB /* PasscodeLockDemo */ = { 254 | isa = PBXGroup; 255 | children = ( 256 | C9D3DF121B91AD11008561EB /* AppDelegate.swift */, 257 | DE6F8E1B1C24BF7500D3EFCF /* LockSplashView.swift */, 258 | C9D3DF141B91AD11008561EB /* PasscodeSettingsViewController.swift */, 259 | C9D3DF161B91AD11008561EB /* Main.storyboard */, 260 | C9D3DF191B91AD11008561EB /* Assets.xcassets */, 261 | C9D3DF1B1B91AD11008561EB /* LaunchScreen.storyboard */, 262 | C9D3DF1E1B91AD11008561EB /* Info.plist */, 263 | C9D3DF431B91B9CD008561EB /* UserDefaultsPasscodeRepository.swift */, 264 | C9D3DF451B91BD0E008561EB /* PasscodeLockConfiguration.swift */, 265 | DE6F8E1D1C24C09400D3EFCF /* CustomPasscodeLockPresenter.swift */, 266 | ); 267 | path = PasscodeLockDemo; 268 | sourceTree = ""; 269 | }; 270 | C9D3DF261B91AD12008561EB /* PasscodeLockDemoTests */ = { 271 | isa = PBXGroup; 272 | children = ( 273 | C9D3DF291B91AD12008561EB /* Info.plist */, 274 | ); 275 | path = PasscodeLockDemoTests; 276 | sourceTree = ""; 277 | }; 278 | C9D3DF311B91AD12008561EB /* PasscodeLockDemoUITests */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | C9D3DF341B91AD12008561EB /* Info.plist */, 282 | ); 283 | path = PasscodeLockDemoUITests; 284 | sourceTree = ""; 285 | }; 286 | C9DC07D11B90B1CE007A4DD0 /* PasscodeLock */ = { 287 | isa = PBXGroup; 288 | children = ( 289 | C9DC07E01B90BBFD007A4DD0 /* PasscodeLock.swift */, 290 | C9DC07F11B90C9DE007A4DD0 /* EnterPasscodeState.swift */, 291 | C9DC07FE1B90D24A007A4DD0 /* SetPasscodeState.swift */, 292 | C9DC08011B90D2BA007A4DD0 /* ConfirmPasscodeState.swift */, 293 | C9DC08041B90D394007A4DD0 /* ChangePasscodeState.swift */, 294 | ); 295 | path = PasscodeLock; 296 | sourceTree = ""; 297 | }; 298 | C9DC07D21B90B1CE007A4DD0 /* Protocols */ = { 299 | isa = PBXGroup; 300 | children = ( 301 | C9DC07D41B90B1F6007A4DD0 /* PasscodeRepositoryType.swift */, 302 | C9DC07D71B90B261007A4DD0 /* PasscodeLockStateType.swift */, 303 | C9DC07DA1B90B730007A4DD0 /* PasscodeLockConfigurationType.swift */, 304 | C9DC07DD1B90BA06007A4DD0 /* PasscodeLockType.swift */, 305 | ); 306 | path = Protocols; 307 | sourceTree = ""; 308 | }; 309 | C9DC07D31B90B1CE007A4DD0 /* Views */ = { 310 | isa = PBXGroup; 311 | children = ( 312 | C9DC08111B90DE1B007A4DD0 /* PasscodeSignPlaceholderView.swift */, 313 | C9DC08131B90DE50007A4DD0 /* PasscodeSignButton.swift */, 314 | C9D3DF0A1B919CE4008561EB /* PasscodeLockView.xib */, 315 | ); 316 | path = Views; 317 | sourceTree = ""; 318 | }; 319 | C9DC07E31B90BCE7007A4DD0 /* PasscodeLock */ = { 320 | isa = PBXGroup; 321 | children = ( 322 | C9DC07E41B90BCF9007A4DD0 /* PasscodeLockTests.swift */, 323 | C9DC08071B90DAE6007A4DD0 /* EnterPasscodeStateTests.swift */, 324 | C9DC080B1B90DC38007A4DD0 /* SetPasscodeStateTests.swift */, 325 | C9DC080D1B90DC8F007A4DD0 /* ConfirmPasscodeStateTests.swift */, 326 | C9DC080F1B90DD91007A4DD0 /* ChangePasscodeStateTests.swift */, 327 | ); 328 | path = PasscodeLock; 329 | sourceTree = ""; 330 | }; 331 | C9DC07E81B90C673007A4DD0 /* Fakes */ = { 332 | isa = PBXGroup; 333 | children = ( 334 | C9DC07E91B90C690007A4DD0 /* FakePasscodeState.swift */, 335 | C9DC07EB1B90C72A007A4DD0 /* FakePasscodeRepository.swift */, 336 | C9DC07ED1B90C7CD007A4DD0 /* FakePasscodeLockConfiguration.swift */, 337 | C9DC07EF1B90C8E1007A4DD0 /* FakePasscodeLockDelegate.swift */, 338 | C9DC08091B90DB09007A4DD0 /* FakePasscodeLock.swift */, 339 | ); 340 | path = Fakes; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXGroup section */ 344 | 345 | /* Begin PBXHeadersBuildPhase section */ 346 | C99EAF3C1B90B05700D61E1B /* Headers */ = { 347 | isa = PBXHeadersBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | C99EAF431B90B05700D61E1B /* PasscodeLock.h in Headers */, 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | }; 354 | /* End PBXHeadersBuildPhase section */ 355 | 356 | /* Begin PBXNativeTarget section */ 357 | C99EAF3E1B90B05700D61E1B /* PasscodeLock */ = { 358 | isa = PBXNativeTarget; 359 | buildConfigurationList = C99EAF531B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLock" */; 360 | buildPhases = ( 361 | C99EAF3A1B90B05700D61E1B /* Sources */, 362 | C99EAF3B1B90B05700D61E1B /* Frameworks */, 363 | C99EAF3C1B90B05700D61E1B /* Headers */, 364 | C99EAF3D1B90B05700D61E1B /* Resources */, 365 | ); 366 | buildRules = ( 367 | ); 368 | dependencies = ( 369 | ); 370 | name = PasscodeLock; 371 | productName = PasscodeLock; 372 | productReference = C99EAF3F1B90B05700D61E1B /* PasscodeLock.framework */; 373 | productType = "com.apple.product-type.framework"; 374 | }; 375 | C99EAF481B90B05700D61E1B /* PasscodeLockTests */ = { 376 | isa = PBXNativeTarget; 377 | buildConfigurationList = C99EAF561B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLockTests" */; 378 | buildPhases = ( 379 | C99EAF451B90B05700D61E1B /* Sources */, 380 | C99EAF461B90B05700D61E1B /* Frameworks */, 381 | C99EAF471B90B05700D61E1B /* Resources */, 382 | ); 383 | buildRules = ( 384 | ); 385 | dependencies = ( 386 | C99EAF4C1B90B05800D61E1B /* PBXTargetDependency */, 387 | ); 388 | name = PasscodeLockTests; 389 | productName = PasscodeLockTests; 390 | productReference = C99EAF491B90B05800D61E1B /* PasscodeLockTests.xctest */; 391 | productType = "com.apple.product-type.bundle.unit-test"; 392 | }; 393 | C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */ = { 394 | isa = PBXNativeTarget; 395 | buildConfigurationList = C9D3DF351B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemo" */; 396 | buildPhases = ( 397 | C9D3DF0C1B91AD11008561EB /* Sources */, 398 | C9D3DF0D1B91AD11008561EB /* Frameworks */, 399 | C9D3DF0E1B91AD11008561EB /* Resources */, 400 | C9D3DF421B91AD7A008561EB /* Embed Frameworks */, 401 | ); 402 | buildRules = ( 403 | ); 404 | dependencies = ( 405 | C9D3DF411B91AD7A008561EB /* PBXTargetDependency */, 406 | ); 407 | name = PasscodeLockDemo; 408 | productName = PasscodeLockDemo; 409 | productReference = C9D3DF101B91AD11008561EB /* PasscodeLockDemo.app */; 410 | productType = "com.apple.product-type.application"; 411 | }; 412 | C9D3DF221B91AD11008561EB /* PasscodeLockDemoTests */ = { 413 | isa = PBXNativeTarget; 414 | buildConfigurationList = C9D3DF381B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoTests" */; 415 | buildPhases = ( 416 | C9D3DF1F1B91AD11008561EB /* Sources */, 417 | C9D3DF201B91AD11008561EB /* Frameworks */, 418 | C9D3DF211B91AD11008561EB /* Resources */, 419 | ); 420 | buildRules = ( 421 | ); 422 | dependencies = ( 423 | C9D3DF251B91AD12008561EB /* PBXTargetDependency */, 424 | ); 425 | name = PasscodeLockDemoTests; 426 | productName = PasscodeLockDemoTests; 427 | productReference = C9D3DF231B91AD11008561EB /* PasscodeLockDemoTests.xctest */; 428 | productType = "com.apple.product-type.bundle.unit-test"; 429 | }; 430 | C9D3DF2D1B91AD12008561EB /* PasscodeLockDemoUITests */ = { 431 | isa = PBXNativeTarget; 432 | buildConfigurationList = C9D3DF3B1B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoUITests" */; 433 | buildPhases = ( 434 | C9D3DF2A1B91AD12008561EB /* Sources */, 435 | C9D3DF2B1B91AD12008561EB /* Frameworks */, 436 | C9D3DF2C1B91AD12008561EB /* Resources */, 437 | ); 438 | buildRules = ( 439 | ); 440 | dependencies = ( 441 | C9D3DF301B91AD12008561EB /* PBXTargetDependency */, 442 | ); 443 | name = PasscodeLockDemoUITests; 444 | productName = PasscodeLockDemoUITests; 445 | productReference = C9D3DF2E1B91AD12008561EB /* PasscodeLockDemoUITests.xctest */; 446 | productType = "com.apple.product-type.bundle.ui-testing"; 447 | }; 448 | /* End PBXNativeTarget section */ 449 | 450 | /* Begin PBXProject section */ 451 | C99EAF361B90B05700D61E1B /* Project object */ = { 452 | isa = PBXProject; 453 | attributes = { 454 | LastSwiftUpdateCheck = 0700; 455 | LastUpgradeCheck = 0900; 456 | ORGANIZATIONNAME = "Yanko Dimitrov"; 457 | TargetAttributes = { 458 | C99EAF3E1B90B05700D61E1B = { 459 | CreatedOnToolsVersion = 7.0; 460 | LastSwiftMigration = 0900; 461 | }; 462 | C99EAF481B90B05700D61E1B = { 463 | CreatedOnToolsVersion = 7.0; 464 | LastSwiftMigration = 0900; 465 | }; 466 | C9D3DF0F1B91AD11008561EB = { 467 | CreatedOnToolsVersion = 7.0; 468 | LastSwiftMigration = 0900; 469 | }; 470 | C9D3DF221B91AD11008561EB = { 471 | CreatedOnToolsVersion = 7.0; 472 | LastSwiftMigration = 0900; 473 | TestTargetID = C9D3DF0F1B91AD11008561EB; 474 | }; 475 | C9D3DF2D1B91AD12008561EB = { 476 | CreatedOnToolsVersion = 7.0; 477 | LastSwiftMigration = 0900; 478 | TestTargetID = C9D3DF0F1B91AD11008561EB; 479 | }; 480 | }; 481 | }; 482 | buildConfigurationList = C99EAF391B90B05700D61E1B /* Build configuration list for PBXProject "PasscodeLock" */; 483 | compatibilityVersion = "Xcode 3.2"; 484 | developmentRegion = English; 485 | hasScannedForEncodings = 0; 486 | knownRegions = ( 487 | en, 488 | Base, 489 | ); 490 | mainGroup = C99EAF351B90B05700D61E1B; 491 | productRefGroup = C99EAF401B90B05700D61E1B /* Products */; 492 | projectDirPath = ""; 493 | projectRoot = ""; 494 | targets = ( 495 | C99EAF3E1B90B05700D61E1B /* PasscodeLock */, 496 | C99EAF481B90B05700D61E1B /* PasscodeLockTests */, 497 | C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */, 498 | C9D3DF221B91AD11008561EB /* PasscodeLockDemoTests */, 499 | C9D3DF2D1B91AD12008561EB /* PasscodeLockDemoUITests */, 500 | ); 501 | }; 502 | /* End PBXProject section */ 503 | 504 | /* Begin PBXResourcesBuildPhase section */ 505 | C99EAF3D1B90B05700D61E1B /* Resources */ = { 506 | isa = PBXResourcesBuildPhase; 507 | buildActionMask = 2147483647; 508 | files = ( 509 | C9DC07FC1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */, 510 | C9D3DF0B1B919CE4008561EB /* PasscodeLockView.xib in Resources */, 511 | ); 512 | runOnlyForDeploymentPostprocessing = 0; 513 | }; 514 | C99EAF471B90B05700D61E1B /* Resources */ = { 515 | isa = PBXResourcesBuildPhase; 516 | buildActionMask = 2147483647; 517 | files = ( 518 | C9DC07FD1B90D0A3007A4DD0 /* PasscodeLock.strings in Resources */, 519 | ); 520 | runOnlyForDeploymentPostprocessing = 0; 521 | }; 522 | C9D3DF0E1B91AD11008561EB /* Resources */ = { 523 | isa = PBXResourcesBuildPhase; 524 | buildActionMask = 2147483647; 525 | files = ( 526 | C9D3DF1D1B91AD11008561EB /* LaunchScreen.storyboard in Resources */, 527 | C9D3DF1A1B91AD11008561EB /* Assets.xcassets in Resources */, 528 | C9D3DF181B91AD11008561EB /* Main.storyboard in Resources */, 529 | ); 530 | runOnlyForDeploymentPostprocessing = 0; 531 | }; 532 | C9D3DF211B91AD11008561EB /* Resources */ = { 533 | isa = PBXResourcesBuildPhase; 534 | buildActionMask = 2147483647; 535 | files = ( 536 | ); 537 | runOnlyForDeploymentPostprocessing = 0; 538 | }; 539 | C9D3DF2C1B91AD12008561EB /* Resources */ = { 540 | isa = PBXResourcesBuildPhase; 541 | buildActionMask = 2147483647; 542 | files = ( 543 | ); 544 | runOnlyForDeploymentPostprocessing = 0; 545 | }; 546 | /* End PBXResourcesBuildPhase section */ 547 | 548 | /* Begin PBXSourcesBuildPhase section */ 549 | C99EAF3A1B90B05700D61E1B /* Sources */ = { 550 | isa = PBXSourcesBuildPhase; 551 | buildActionMask = 2147483647; 552 | files = ( 553 | C9DC07FF1B90D24A007A4DD0 /* SetPasscodeState.swift in Sources */, 554 | C9DC08051B90D394007A4DD0 /* ChangePasscodeState.swift in Sources */, 555 | C9DC08021B90D2BA007A4DD0 /* ConfirmPasscodeState.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 | DE6F8E1C1C24BF7500D3EFCF /* LockSplashView.swift in Sources */, 603 | C9D3DF461B91BD0E008561EB /* PasscodeLockConfiguration.swift in Sources */, 604 | C9D3DF151B91AD11008561EB /* PasscodeSettingsViewController.swift in Sources */, 605 | DE6F8E1E1C24C09400D3EFCF /* CustomPasscodeLockPresenter.swift in Sources */, 606 | C9D3DF131B91AD11008561EB /* AppDelegate.swift in Sources */, 607 | ); 608 | runOnlyForDeploymentPostprocessing = 0; 609 | }; 610 | C9D3DF1F1B91AD11008561EB /* Sources */ = { 611 | isa = PBXSourcesBuildPhase; 612 | buildActionMask = 2147483647; 613 | files = ( 614 | ); 615 | runOnlyForDeploymentPostprocessing = 0; 616 | }; 617 | C9D3DF2A1B91AD12008561EB /* Sources */ = { 618 | isa = PBXSourcesBuildPhase; 619 | buildActionMask = 2147483647; 620 | files = ( 621 | ); 622 | runOnlyForDeploymentPostprocessing = 0; 623 | }; 624 | /* End PBXSourcesBuildPhase section */ 625 | 626 | /* Begin PBXTargetDependency section */ 627 | C99EAF4C1B90B05800D61E1B /* PBXTargetDependency */ = { 628 | isa = PBXTargetDependency; 629 | target = C99EAF3E1B90B05700D61E1B /* PasscodeLock */; 630 | targetProxy = C99EAF4B1B90B05800D61E1B /* PBXContainerItemProxy */; 631 | }; 632 | C9D3DF251B91AD12008561EB /* PBXTargetDependency */ = { 633 | isa = PBXTargetDependency; 634 | target = C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */; 635 | targetProxy = C9D3DF241B91AD12008561EB /* PBXContainerItemProxy */; 636 | }; 637 | C9D3DF301B91AD12008561EB /* PBXTargetDependency */ = { 638 | isa = PBXTargetDependency; 639 | target = C9D3DF0F1B91AD11008561EB /* PasscodeLockDemo */; 640 | targetProxy = C9D3DF2F1B91AD12008561EB /* PBXContainerItemProxy */; 641 | }; 642 | C9D3DF411B91AD7A008561EB /* PBXTargetDependency */ = { 643 | isa = PBXTargetDependency; 644 | target = C99EAF3E1B90B05700D61E1B /* PasscodeLock */; 645 | targetProxy = C9D3DF401B91AD7A008561EB /* PBXContainerItemProxy */; 646 | }; 647 | /* End PBXTargetDependency section */ 648 | 649 | /* Begin PBXVariantGroup section */ 650 | C9D3DF161B91AD11008561EB /* Main.storyboard */ = { 651 | isa = PBXVariantGroup; 652 | children = ( 653 | C9D3DF171B91AD11008561EB /* Base */, 654 | ); 655 | name = Main.storyboard; 656 | sourceTree = ""; 657 | }; 658 | C9D3DF1B1B91AD11008561EB /* LaunchScreen.storyboard */ = { 659 | isa = PBXVariantGroup; 660 | children = ( 661 | C9D3DF1C1B91AD11008561EB /* Base */, 662 | ); 663 | name = LaunchScreen.storyboard; 664 | sourceTree = ""; 665 | }; 666 | C9DC07FA1B90D0A3007A4DD0 /* PasscodeLock.strings */ = { 667 | isa = PBXVariantGroup; 668 | children = ( 669 | C9DC07FB1B90D0A3007A4DD0 /* en */, 670 | ); 671 | name = PasscodeLock.strings; 672 | sourceTree = ""; 673 | }; 674 | /* End PBXVariantGroup section */ 675 | 676 | /* Begin XCBuildConfiguration section */ 677 | C99EAF511B90B05800D61E1B /* Debug */ = { 678 | isa = XCBuildConfiguration; 679 | buildSettings = { 680 | ALWAYS_SEARCH_USER_PATHS = NO; 681 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 682 | CLANG_CXX_LIBRARY = "libc++"; 683 | CLANG_ENABLE_MODULES = YES; 684 | CLANG_ENABLE_OBJC_ARC = YES; 685 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 686 | CLANG_WARN_BOOL_CONVERSION = YES; 687 | CLANG_WARN_COMMA = YES; 688 | CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; 696 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 697 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 698 | CLANG_WARN_STRICT_PROTOTYPES = YES; 699 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 700 | CLANG_WARN_UNREACHABLE_CODE = YES; 701 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 702 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 703 | COPY_PHASE_STRIP = NO; 704 | CURRENT_PROJECT_VERSION = 1; 705 | DEBUG_INFORMATION_FORMAT = dwarf; 706 | ENABLE_STRICT_OBJC_MSGSEND = YES; 707 | ENABLE_TESTABILITY = YES; 708 | GCC_C_LANGUAGE_STANDARD = gnu99; 709 | GCC_DYNAMIC_NO_PIC = NO; 710 | GCC_NO_COMMON_BLOCKS = YES; 711 | GCC_OPTIMIZATION_LEVEL = 0; 712 | GCC_PREPROCESSOR_DEFINITIONS = ( 713 | "DEBUG=1", 714 | "$(inherited)", 715 | ); 716 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 717 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 718 | GCC_WARN_UNDECLARED_SELECTOR = YES; 719 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 720 | GCC_WARN_UNUSED_FUNCTION = YES; 721 | GCC_WARN_UNUSED_VARIABLE = YES; 722 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 723 | MTL_ENABLE_DEBUG_INFO = YES; 724 | ONLY_ACTIVE_ARCH = YES; 725 | SDKROOT = iphoneos; 726 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 727 | SWIFT_VERSION = 4.0; 728 | TARGETED_DEVICE_FAMILY = "1,2"; 729 | VERSIONING_SYSTEM = "apple-generic"; 730 | VERSION_INFO_PREFIX = ""; 731 | }; 732 | name = Debug; 733 | }; 734 | C99EAF521B90B05800D61E1B /* Release */ = { 735 | isa = XCBuildConfiguration; 736 | buildSettings = { 737 | ALWAYS_SEARCH_USER_PATHS = NO; 738 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 739 | CLANG_CXX_LIBRARY = "libc++"; 740 | CLANG_ENABLE_MODULES = YES; 741 | CLANG_ENABLE_OBJC_ARC = YES; 742 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 743 | CLANG_WARN_BOOL_CONVERSION = YES; 744 | CLANG_WARN_COMMA = YES; 745 | CLANG_WARN_CONSTANT_CONVERSION = YES; 746 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 747 | CLANG_WARN_EMPTY_BODY = YES; 748 | CLANG_WARN_ENUM_CONVERSION = YES; 749 | CLANG_WARN_INFINITE_RECURSION = YES; 750 | CLANG_WARN_INT_CONVERSION = YES; 751 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 752 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 753 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 754 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 755 | CLANG_WARN_STRICT_PROTOTYPES = YES; 756 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 757 | CLANG_WARN_UNREACHABLE_CODE = YES; 758 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 759 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 760 | COPY_PHASE_STRIP = NO; 761 | CURRENT_PROJECT_VERSION = 1; 762 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 763 | ENABLE_NS_ASSERTIONS = NO; 764 | ENABLE_STRICT_OBJC_MSGSEND = YES; 765 | GCC_C_LANGUAGE_STANDARD = gnu99; 766 | GCC_NO_COMMON_BLOCKS = YES; 767 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 768 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 769 | GCC_WARN_UNDECLARED_SELECTOR = YES; 770 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 771 | GCC_WARN_UNUSED_FUNCTION = YES; 772 | GCC_WARN_UNUSED_VARIABLE = YES; 773 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 774 | MTL_ENABLE_DEBUG_INFO = NO; 775 | SDKROOT = iphoneos; 776 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 777 | SWIFT_VERSION = 4.0; 778 | TARGETED_DEVICE_FAMILY = "1,2"; 779 | VALIDATE_PRODUCT = YES; 780 | VERSIONING_SYSTEM = "apple-generic"; 781 | VERSION_INFO_PREFIX = ""; 782 | }; 783 | name = Release; 784 | }; 785 | C99EAF541B90B05800D61E1B /* Debug */ = { 786 | isa = XCBuildConfiguration; 787 | buildSettings = { 788 | CLANG_ENABLE_MODULES = YES; 789 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 790 | DEFINES_MODULE = YES; 791 | DYLIB_COMPATIBILITY_VERSION = 1; 792 | DYLIB_CURRENT_VERSION = 1; 793 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 794 | INFOPLIST_FILE = PasscodeLock/Info.plist; 795 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 796 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 797 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLock; 798 | PRODUCT_NAME = "$(TARGET_NAME)"; 799 | SKIP_INSTALL = YES; 800 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 801 | SWIFT_VERSION = 4.0; 802 | }; 803 | name = Debug; 804 | }; 805 | C99EAF551B90B05800D61E1B /* Release */ = { 806 | isa = XCBuildConfiguration; 807 | buildSettings = { 808 | CLANG_ENABLE_MODULES = YES; 809 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 810 | DEFINES_MODULE = YES; 811 | DYLIB_COMPATIBILITY_VERSION = 1; 812 | DYLIB_CURRENT_VERSION = 1; 813 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 814 | INFOPLIST_FILE = PasscodeLock/Info.plist; 815 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 816 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 817 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLock; 818 | PRODUCT_NAME = "$(TARGET_NAME)"; 819 | SKIP_INSTALL = YES; 820 | SWIFT_VERSION = 4.0; 821 | }; 822 | name = Release; 823 | }; 824 | C99EAF571B90B05800D61E1B /* Debug */ = { 825 | isa = XCBuildConfiguration; 826 | buildSettings = { 827 | CLANG_ENABLE_MODULES = YES; 828 | INFOPLIST_FILE = PasscodeLockTests/Info.plist; 829 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 830 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockTests; 831 | PRODUCT_NAME = "$(TARGET_NAME)"; 832 | SWIFT_OBJC_BRIDGING_HEADER = "PasscodeLockTests/PasscodeLockTests-Bridging-Header.h"; 833 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 834 | SWIFT_VERSION = 3.0; 835 | }; 836 | name = Debug; 837 | }; 838 | C99EAF581B90B05800D61E1B /* Release */ = { 839 | isa = XCBuildConfiguration; 840 | buildSettings = { 841 | CLANG_ENABLE_MODULES = YES; 842 | INFOPLIST_FILE = PasscodeLockTests/Info.plist; 843 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 844 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockTests; 845 | PRODUCT_NAME = "$(TARGET_NAME)"; 846 | SWIFT_OBJC_BRIDGING_HEADER = "PasscodeLockTests/PasscodeLockTests-Bridging-Header.h"; 847 | SWIFT_VERSION = 3.0; 848 | }; 849 | name = Release; 850 | }; 851 | C9D3DF361B91AD12008561EB /* Debug */ = { 852 | isa = XCBuildConfiguration; 853 | buildSettings = { 854 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 855 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 856 | INFOPLIST_FILE = PasscodeLockDemo/Info.plist; 857 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 858 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 859 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemo; 860 | PRODUCT_NAME = "$(TARGET_NAME)"; 861 | SWIFT_VERSION = 4.0; 862 | TARGETED_DEVICE_FAMILY = "1,2"; 863 | }; 864 | name = Debug; 865 | }; 866 | C9D3DF371B91AD12008561EB /* Release */ = { 867 | isa = XCBuildConfiguration; 868 | buildSettings = { 869 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 870 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 871 | INFOPLIST_FILE = PasscodeLockDemo/Info.plist; 872 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 873 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 874 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemo; 875 | PRODUCT_NAME = "$(TARGET_NAME)"; 876 | SWIFT_VERSION = 4.0; 877 | TARGETED_DEVICE_FAMILY = "1,2"; 878 | }; 879 | name = Release; 880 | }; 881 | C9D3DF391B91AD12008561EB /* Debug */ = { 882 | isa = XCBuildConfiguration; 883 | buildSettings = { 884 | BUNDLE_LOADER = "$(TEST_HOST)"; 885 | INFOPLIST_FILE = PasscodeLockDemoTests/Info.plist; 886 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 887 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 888 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoTests; 889 | PRODUCT_NAME = "$(TARGET_NAME)"; 890 | SWIFT_VERSION = 4.0; 891 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PasscodeLockDemo.app/PasscodeLockDemo"; 892 | }; 893 | name = Debug; 894 | }; 895 | C9D3DF3A1B91AD12008561EB /* Release */ = { 896 | isa = XCBuildConfiguration; 897 | buildSettings = { 898 | BUNDLE_LOADER = "$(TEST_HOST)"; 899 | INFOPLIST_FILE = PasscodeLockDemoTests/Info.plist; 900 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 901 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 902 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoTests; 903 | PRODUCT_NAME = "$(TARGET_NAME)"; 904 | SWIFT_VERSION = 4.0; 905 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PasscodeLockDemo.app/PasscodeLockDemo"; 906 | }; 907 | name = Release; 908 | }; 909 | C9D3DF3C1B91AD12008561EB /* Debug */ = { 910 | isa = XCBuildConfiguration; 911 | buildSettings = { 912 | INFOPLIST_FILE = PasscodeLockDemoUITests/Info.plist; 913 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 914 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 915 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoUITests; 916 | PRODUCT_NAME = "$(TARGET_NAME)"; 917 | SWIFT_VERSION = 4.0; 918 | TEST_TARGET_NAME = PasscodeLockDemo; 919 | USES_XCTRUNNER = YES; 920 | }; 921 | name = Debug; 922 | }; 923 | C9D3DF3D1B91AD12008561EB /* Release */ = { 924 | isa = XCBuildConfiguration; 925 | buildSettings = { 926 | INFOPLIST_FILE = PasscodeLockDemoUITests/Info.plist; 927 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 928 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 929 | PRODUCT_BUNDLE_IDENTIFIER = com.yankodimitrov.PasscodeLockDemoUITests; 930 | PRODUCT_NAME = "$(TARGET_NAME)"; 931 | SWIFT_VERSION = 4.0; 932 | TEST_TARGET_NAME = PasscodeLockDemo; 933 | USES_XCTRUNNER = YES; 934 | }; 935 | name = Release; 936 | }; 937 | /* End XCBuildConfiguration section */ 938 | 939 | /* Begin XCConfigurationList section */ 940 | C99EAF391B90B05700D61E1B /* Build configuration list for PBXProject "PasscodeLock" */ = { 941 | isa = XCConfigurationList; 942 | buildConfigurations = ( 943 | C99EAF511B90B05800D61E1B /* Debug */, 944 | C99EAF521B90B05800D61E1B /* Release */, 945 | ); 946 | defaultConfigurationIsVisible = 0; 947 | defaultConfigurationName = Release; 948 | }; 949 | C99EAF531B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLock" */ = { 950 | isa = XCConfigurationList; 951 | buildConfigurations = ( 952 | C99EAF541B90B05800D61E1B /* Debug */, 953 | C99EAF551B90B05800D61E1B /* Release */, 954 | ); 955 | defaultConfigurationIsVisible = 0; 956 | defaultConfigurationName = Release; 957 | }; 958 | C99EAF561B90B05800D61E1B /* Build configuration list for PBXNativeTarget "PasscodeLockTests" */ = { 959 | isa = XCConfigurationList; 960 | buildConfigurations = ( 961 | C99EAF571B90B05800D61E1B /* Debug */, 962 | C99EAF581B90B05800D61E1B /* Release */, 963 | ); 964 | defaultConfigurationIsVisible = 0; 965 | defaultConfigurationName = Release; 966 | }; 967 | C9D3DF351B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemo" */ = { 968 | isa = XCConfigurationList; 969 | buildConfigurations = ( 970 | C9D3DF361B91AD12008561EB /* Debug */, 971 | C9D3DF371B91AD12008561EB /* Release */, 972 | ); 973 | defaultConfigurationIsVisible = 0; 974 | defaultConfigurationName = Release; 975 | }; 976 | C9D3DF381B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoTests" */ = { 977 | isa = XCConfigurationList; 978 | buildConfigurations = ( 979 | C9D3DF391B91AD12008561EB /* Debug */, 980 | C9D3DF3A1B91AD12008561EB /* Release */, 981 | ); 982 | defaultConfigurationIsVisible = 0; 983 | defaultConfigurationName = Release; 984 | }; 985 | C9D3DF3B1B91AD12008561EB /* Build configuration list for PBXNativeTarget "PasscodeLockDemoUITests" */ = { 986 | isa = XCConfigurationList; 987 | buildConfigurations = ( 988 | C9D3DF3C1B91AD12008561EB /* Debug */, 989 | C9D3DF3D1B91AD12008561EB /* Release */, 990 | ); 991 | defaultConfigurationIsVisible = 0; 992 | defaultConfigurationName = Release; 993 | }; 994 | /* End XCConfigurationList section */ 995 | }; 996 | rootObject = C99EAF361B90B05700D61E1B /* Project object */; 997 | } 998 | -------------------------------------------------------------------------------- /PasscodeLock.xcodeproj/xcshareddata/xcschemes/PasscodeLock.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /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 bundle = bundleForResource(name, ofType: "strings") 15 | 16 | return NSLocalizedString(key, tableName: name, bundle: bundle, comment: comment) 17 | } 18 | 19 | func bundleForResource(_ name: String, ofType type: String) -> Bundle { 20 | 21 | if(Bundle.main.path(forResource: name, ofType: type) != nil) { 22 | return Bundle.main 23 | } 24 | 25 | return Bundle(for: PasscodeLock.self) 26 | } 27 | -------------------------------------------------------------------------------- /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("PasscodeLockChangeTitle", comment: "Change passcode title") 21 | description = localizedStringFor("PasscodeLockChangeDescription", comment: "Change passcode description") 22 | } 23 | 24 | func acceptPasscode(_ passcode: [String], fromLock lock: PasscodeLockType) { 25 | 26 | guard let currentPasscode = lock.repository.passcode else { 27 | return 28 | } 29 | 30 | if passcode == currentPasscode { 31 | 32 | let nextState = SetPasscodeState() 33 | 34 | lock.changeStateTo(nextState) 35 | 36 | } else { 37 | 38 | lock.delegate?.passcodeLockDidFail(lock) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 | fileprivate var passcodeToConfirm: [String] 19 | 20 | init(passcode: [String]) { 21 | 22 | passcodeToConfirm = passcode 23 | title = localizedStringFor("PasscodeLockConfirmTitle", comment: "Confirm passcode title") 24 | description = localizedStringFor("PasscodeLockConfirmDescription", comment: "Confirm passcode description") 25 | } 26 | 27 | func acceptPasscode(_ passcode: [String], fromLock lock: PasscodeLockType) { 28 | 29 | if passcode == passcodeToConfirm { 30 | 31 | lock.repository.savePasscode(passcode) 32 | lock.delegate?.passcodeLockDidSucceed(lock) 33 | 34 | } else { 35 | 36 | let mismatchTitle = localizedStringFor("PasscodeLockMismatchTitle", comment: "Passcode mismatch title") 37 | let mismatchDescription = localizedStringFor("PasscodeLockMismatchDescription", comment: "Passcode mismatch description") 38 | 39 | let nextState = SetPasscodeState(title: mismatchTitle, description: mismatchDescription) 40 | 41 | lock.changeStateTo(nextState) 42 | lock.delegate?.passcodeLockDidFail(lock) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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 = "passcode.lock.incorrect.passcode.notification" 12 | 13 | struct EnterPasscodeState: PasscodeLockStateType { 14 | 15 | let title: String 16 | let description: String 17 | let isCancellableAction: Bool 18 | var isTouchIDAllowed = true 19 | 20 | static let incorrectPasscodeAttemptsKey = "incorrectPasscodeAttempts" 21 | static var incorrectPasscodeAttempts: Int { 22 | get { 23 | return UserDefaults.standard.integer(forKey: incorrectPasscodeAttemptsKey) 24 | } 25 | set { 26 | UserDefaults.standard.set(newValue, forKey: incorrectPasscodeAttemptsKey) 27 | } 28 | } 29 | 30 | private var isNotificationSent = false 31 | 32 | init(allowCancellation: Bool = false) { 33 | 34 | isCancellableAction = allowCancellation 35 | title = localizedStringFor("PasscodeLockEnterTitle", comment: "Enter passcode title") 36 | description = localizedStringFor("PasscodeLockEnterDescription", comment: "Enter passcode description") 37 | } 38 | 39 | mutating func acceptPasscode(_ passcode: [String], fromLock lock: PasscodeLockType) { 40 | 41 | guard let currentPasscode = lock.repository.passcode else { 42 | return 43 | } 44 | 45 | var incorrectPasscodeAttempts = EnterPasscodeState.incorrectPasscodeAttempts 46 | if passcode == currentPasscode { 47 | 48 | lock.delegate?.passcodeLockDidSucceed(lock) 49 | incorrectPasscodeAttempts = 0 50 | } else { 51 | 52 | incorrectPasscodeAttempts += 1 53 | 54 | if incorrectPasscodeAttempts >= lock.configuration.maximumInccorectPasscodeAttempts { 55 | 56 | postNotification() 57 | incorrectPasscodeAttempts = 0 58 | } 59 | 60 | lock.delegate?.passcodeLockDidFail(lock) 61 | } 62 | 63 | EnterPasscodeState.incorrectPasscodeAttempts = incorrectPasscodeAttempts 64 | } 65 | 66 | fileprivate mutating func postNotification() { 67 | 68 | guard !isNotificationSent else { return } 69 | 70 | let center = NotificationCenter.default 71 | 72 | center.post(name: Notification.Name(rawValue: PasscodeLockIncorrectPasscodeNotification), object: nil) 73 | 74 | isNotificationSent = true 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /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 | 14 | open weak var delegate: PasscodeLockTypeDelegate? 15 | open let configuration: PasscodeLockConfigurationType 16 | 17 | open var repository: PasscodeRepositoryType { 18 | return configuration.repository 19 | } 20 | 21 | open var state: PasscodeLockStateType { 22 | return lockState 23 | } 24 | 25 | open var isTouchIDAllowed: Bool { 26 | return isTouchIDEnabled() && configuration.isTouchIDAllowed && lockState.isTouchIDAllowed 27 | } 28 | 29 | fileprivate var lockState: PasscodeLockStateType 30 | fileprivate lazy var passcode = [String]() 31 | 32 | public init(state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType) { 33 | 34 | precondition(configuration.passcodeLength > 0, "Passcode length sould be greather than zero.") 35 | 36 | self.lockState = state 37 | self.configuration = configuration 38 | } 39 | 40 | open func addSign(_ sign: String) { 41 | 42 | passcode.append(sign) 43 | delegate?.passcodeLock(self, addedSignAtIndex: passcode.count - 1) 44 | 45 | if passcode.count >= configuration.passcodeLength { 46 | 47 | // handles "requires exclusive access" error at Swift 4 48 | var lockStateCopy = lockState 49 | lockStateCopy.acceptPasscode(passcode, fromLock: self) 50 | passcode.removeAll(keepingCapacity: true) 51 | } 52 | } 53 | 54 | open func removeSign() { 55 | 56 | guard passcode.count > 0 else { return } 57 | 58 | passcode.removeLast() 59 | delegate?.passcodeLock(self, removedSignAtIndex: passcode.count) 60 | } 61 | 62 | open func changeStateTo(_ state: PasscodeLockStateType) { 63 | 64 | lockState = state 65 | delegate?.passcodeLockDidChangeState(self) 66 | } 67 | 68 | open func authenticateWithBiometrics() { 69 | 70 | guard isTouchIDAllowed else { return } 71 | 72 | let context = LAContext() 73 | let reason: String 74 | if let configReason = configuration.touchIdReason { 75 | reason = configReason 76 | } else { 77 | reason = localizedStringFor("PasscodeLockTouchIDReason", comment: "TouchID authentication reason") 78 | } 79 | 80 | context.localizedFallbackTitle = localizedStringFor("PasscodeLockTouchIDButton", comment: "TouchID authentication fallback button") 81 | 82 | context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { 83 | success, error in 84 | 85 | self.handleTouchIDResult(success) 86 | } 87 | } 88 | 89 | fileprivate func handleTouchIDResult(_ success: Bool) { 90 | 91 | DispatchQueue.main.async { 92 | 93 | if success { 94 | EnterPasscodeState.incorrectPasscodeAttempts = 0 95 | self.delegate?.passcodeLockDidSucceed(self) 96 | } 97 | } 98 | } 99 | 100 | fileprivate func isTouchIDEnabled() -> Bool { 101 | 102 | let context = LAContext() 103 | 104 | return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /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("PasscodeLockSetTitle", comment: "Set passcode title") 27 | description = localizedStringFor("PasscodeLockSetDescription", comment: "Set passcode description") 28 | } 29 | 30 | func acceptPasscode(_ passcode: [String], fromLock lock: PasscodeLockType) { 31 | 32 | let nextState = ConfirmPasscodeState(passcode: passcode) 33 | 34 | lock.changeStateTo(nextState) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 | fileprivate var mainWindow: UIWindow? 14 | 15 | fileprivate lazy var passcodeLockWindow: UIWindow = { 16 | 17 | let window = UIWindow(frame: UIScreen.main.bounds) 18 | 19 | window.windowLevel = 0 20 | window.makeKeyAndVisible() 21 | 22 | return window 23 | }() 24 | 25 | fileprivate let passcodeConfiguration: PasscodeLockConfigurationType 26 | open var isPasscodePresented = false 27 | 28 | open let passcodeLockVC: PasscodeLockViewController 29 | 30 | public init(mainWindow window: UIWindow?, configuration: PasscodeLockConfigurationType, viewController: PasscodeLockViewController) { 31 | 32 | mainWindow = window 33 | mainWindow?.windowLevel = 1 34 | passcodeConfiguration = configuration 35 | 36 | passcodeLockVC = viewController 37 | } 38 | 39 | public convenience init(mainWindow window: UIWindow?, configuration: PasscodeLockConfigurationType) { 40 | let passcodeLockVC = PasscodeLockViewController(state: .enterPasscode, configuration: configuration) 41 | 42 | self.init(mainWindow: window, configuration: configuration, viewController: passcodeLockVC) 43 | } 44 | 45 | // HACK: below function that handles not presenting the keyboard in case Passcode is presented 46 | // is a smell in the code that had to be introduced for iOS9 where Apple decided to move the keyboard 47 | // in a UIRemoteKeyboardWindow. 48 | // This doesn't allow our Passcode Lock window to move on top of keyboard. 49 | // Setting a higher windowLevel to our window or even trying to change keyboards' 50 | // windowLevel has been tried without luck. 51 | // 52 | // Revise in a later version and remove the hack if not needed 53 | func toggleKeyboardVisibility(hide: Bool) { 54 | if let keyboardWindow = UIApplication.shared.windows.last, 55 | keyboardWindow.description.hasPrefix(" PasscodeLockStateType { 20 | 21 | switch self { 22 | case .enterPasscode: return EnterPasscodeState() 23 | case .setPasscode: return SetPasscodeState() 24 | case .changePasscode: return ChangePasscodeState() 25 | case .removePasscode: return EnterPasscodeState(allowCancellation: true) 26 | } 27 | } 28 | } 29 | 30 | @IBOutlet open weak var titleLabel: UILabel? 31 | @IBOutlet open weak var descriptionLabel: UILabel? 32 | @IBOutlet open var placeholders: [PasscodeSignPlaceholderView] = [PasscodeSignPlaceholderView]() 33 | @IBOutlet open weak var cancelButton: UIButton? 34 | @IBOutlet open weak var deleteSignButton: UIButton? 35 | @IBOutlet open weak var touchIDButton: UIButton? 36 | @IBOutlet open weak var placeholdersX: NSLayoutConstraint? 37 | 38 | open var successCallback: ((_ lock: PasscodeLockType) -> Void)? 39 | open var dismissCompletionCallback: (()->Void)? 40 | open var animateOnDismiss: Bool 41 | open var notificationCenter: NotificationCenter? 42 | 43 | internal let passcodeConfiguration: PasscodeLockConfigurationType 44 | internal var passcodeLock: PasscodeLockType 45 | internal var isPlaceholdersAnimationCompleted = true 46 | 47 | fileprivate var shouldTryToAuthenticateWithBiometrics = true 48 | 49 | // MARK: - Initializers 50 | 51 | public init(state: PasscodeLockStateType, configuration: PasscodeLockConfigurationType, animateOnDismiss: Bool = true, nibName: String = "PasscodeLockView", bundle: Bundle? = nil) { 52 | 53 | self.animateOnDismiss = animateOnDismiss 54 | 55 | passcodeConfiguration = configuration 56 | passcodeLock = PasscodeLock(state: state, configuration: configuration) 57 | 58 | let bundleToUse = bundle ?? bundleForResource(nibName, ofType: "nib") 59 | 60 | super.init(nibName: nibName, bundle: bundleToUse) 61 | 62 | passcodeLock.delegate = self 63 | notificationCenter = NotificationCenter.default 64 | } 65 | 66 | public convenience init(state: LockState, configuration: PasscodeLockConfigurationType, animateOnDismiss: Bool = true) { 67 | 68 | self.init(state: state.getState(), configuration: configuration, animateOnDismiss: animateOnDismiss) 69 | } 70 | 71 | public required init(coder aDecoder: NSCoder) { 72 | fatalError("init(coder:) has not been implemented") 73 | } 74 | 75 | deinit { 76 | 77 | clearEvents() 78 | } 79 | 80 | // MARK: - View 81 | 82 | open override func viewDidLoad() { 83 | super.viewDidLoad() 84 | 85 | deleteSignButton?.isEnabled = false 86 | 87 | setupEvents() 88 | } 89 | 90 | open override func viewWillAppear(_ animated: Bool) { 91 | super.viewWillAppear(animated) 92 | 93 | updatePasscodeView() 94 | } 95 | 96 | open override func viewDidAppear(_ animated: Bool) { 97 | super.viewDidAppear(animated) 98 | 99 | if shouldTryToAuthenticateWithBiometrics && passcodeConfiguration.shouldRequestTouchIDImmediately { 100 | 101 | authenticateWithBiometrics() 102 | } 103 | } 104 | 105 | internal func updatePasscodeView() { 106 | 107 | titleLabel?.text = passcodeLock.state.title 108 | descriptionLabel?.text = passcodeLock.state.description 109 | cancelButton?.isHidden = !passcodeLock.state.isCancellableAction 110 | touchIDButton?.isHidden = !passcodeLock.isTouchIDAllowed 111 | } 112 | 113 | // MARK: - Events 114 | 115 | fileprivate func setupEvents() { 116 | 117 | notificationCenter?.addObserver(self, selector: #selector(PasscodeLockViewController.appWillEnterForegroundHandler(_:)), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) 118 | notificationCenter?.addObserver(self, selector: #selector(PasscodeLockViewController.appDidEnterBackgroundHandler(_:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) 119 | } 120 | 121 | fileprivate func clearEvents() { 122 | 123 | notificationCenter?.removeObserver(self, name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) 124 | notificationCenter?.removeObserver(self, name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) 125 | } 126 | 127 | @objc open func appWillEnterForegroundHandler(_ notification: Notification) { 128 | 129 | if passcodeConfiguration.shouldRequestTouchIDImmediately { 130 | authenticateWithBiometrics() 131 | } 132 | } 133 | 134 | @objc open func appDidEnterBackgroundHandler(_ notification: Notification) { 135 | 136 | shouldTryToAuthenticateWithBiometrics = false 137 | } 138 | 139 | // MARK: - Actions 140 | 141 | @IBAction func passcodeSignButtonTap(_ sender: PasscodeSignButton) { 142 | 143 | guard isPlaceholdersAnimationCompleted else { return } 144 | 145 | passcodeLock.addSign(sender.passcodeSign) 146 | } 147 | 148 | @IBAction func cancelButtonTap(_ sender: UIButton) { 149 | 150 | dismissPasscodeLock(passcodeLock) 151 | } 152 | 153 | @IBAction func deleteSignButtonTap(_ sender: UIButton) { 154 | 155 | passcodeLock.removeSign() 156 | } 157 | 158 | @IBAction func touchIDButtonTap(_ sender: UIButton) { 159 | 160 | passcodeLock.authenticateWithBiometrics() 161 | } 162 | 163 | open func authenticateWithBiometrics() { 164 | 165 | guard passcodeConfiguration.repository.hasPasscode else { return } 166 | 167 | if passcodeLock.isTouchIDAllowed { 168 | 169 | passcodeLock.authenticateWithBiometrics() 170 | } 171 | } 172 | 173 | internal func dismissPasscodeLock(_ lock: PasscodeLockType, completionHandler: (() -> Void)? = nil) { 174 | 175 | // if presented as modal 176 | if presentingViewController?.presentedViewController == self { 177 | 178 | dismiss(animated: animateOnDismiss, completion: { [weak self] in 179 | 180 | self?.dismissCompletionCallback?() 181 | 182 | completionHandler?() 183 | }) 184 | 185 | return 186 | 187 | // if pushed in a navigation controller 188 | } else if navigationController != nil { 189 | 190 | navigationController?.popViewController(animated: animateOnDismiss) 191 | } 192 | 193 | dismissCompletionCallback?() 194 | 195 | completionHandler?() 196 | } 197 | 198 | // MARK: - Animations 199 | 200 | internal func animateWrongPassword() { 201 | 202 | deleteSignButton?.isEnabled = false 203 | isPlaceholdersAnimationCompleted = false 204 | 205 | animatePlaceholders(placeholders, toState: .error) 206 | 207 | placeholdersX?.constant = -40 208 | view.layoutIfNeeded() 209 | 210 | UIView.animate( 211 | withDuration: 0.5, 212 | delay: 0, 213 | usingSpringWithDamping: 0.2, 214 | initialSpringVelocity: 0, 215 | options: [], 216 | animations: { 217 | 218 | self.placeholdersX?.constant = 0 219 | self.view.layoutIfNeeded() 220 | }, 221 | completion: { completed in 222 | 223 | self.isPlaceholdersAnimationCompleted = true 224 | self.animatePlaceholders(self.placeholders, toState: .inactive) 225 | }) 226 | } 227 | 228 | internal func animatePlaceholders(_ placeholders: [PasscodeSignPlaceholderView], toState state: PasscodeSignPlaceholderView.State) { 229 | 230 | for placeholder in placeholders { 231 | 232 | placeholder.animateState(state) 233 | } 234 | } 235 | 236 | fileprivate func animatePlacehodlerAtIndex(_ index: Int, toState state: PasscodeSignPlaceholderView.State) { 237 | 238 | guard index < placeholders.count && index >= 0 else { return } 239 | 240 | placeholders[index].animateState(state) 241 | } 242 | 243 | // MARK: - PasscodeLockDelegate 244 | 245 | open func passcodeLockDidSucceed(_ lock: PasscodeLockType) { 246 | 247 | deleteSignButton?.isEnabled = true 248 | animatePlaceholders(placeholders, toState: .inactive) 249 | dismissPasscodeLock(lock, completionHandler: { [weak self] in 250 | self?.successCallback?(lock) 251 | }) 252 | } 253 | 254 | open func passcodeLockDidFail(_ lock: PasscodeLockType) { 255 | 256 | animateWrongPassword() 257 | } 258 | 259 | open func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 260 | 261 | updatePasscodeView() 262 | animatePlaceholders(placeholders, toState: .inactive) 263 | deleteSignButton?.isEnabled = false 264 | } 265 | 266 | open func passcodeLock(_ lock: PasscodeLockType, addedSignAtIndex index: Int) { 267 | 268 | animatePlacehodlerAtIndex(index, toState: .active) 269 | deleteSignButton?.isEnabled = true 270 | } 271 | 272 | open func passcodeLock(_ lock: PasscodeLockType, removedSignAtIndex index: Int) { 273 | 274 | animatePlacehodlerAtIndex(index, toState: .inactive) 275 | 276 | if index == 0 { 277 | 278 | deleteSignButton?.isEnabled = false 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /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 | 13 | var repository: PasscodeRepositoryType {get} 14 | var passcodeLength: Int {get} 15 | var isTouchIDAllowed: Bool {get set} 16 | var shouldRequestTouchIDImmediately: Bool {get} 17 | var touchIdReason: String? {get set} 18 | var maximumInccorectPasscodeAttempts: Int {get} 19 | } 20 | 21 | // set configuration optionals 22 | public extension PasscodeLockConfigurationType { 23 | var passcodeLength: Int { 24 | return 4 25 | } 26 | 27 | var maximumInccorectPasscodeAttempts: Int { 28 | return -1 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 acceptPasscode(_ passcode: [String], fromLock 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 | 13 | weak var delegate: PasscodeLockTypeDelegate? {get set} 14 | var configuration: PasscodeLockConfigurationType {get} 15 | var repository: PasscodeRepositoryType {get} 16 | var state: PasscodeLockStateType {get} 17 | var isTouchIDAllowed: Bool {get} 18 | 19 | func addSign(_ sign: String) 20 | func removeSign() 21 | func changeStateTo(_ state: PasscodeLockStateType) 22 | func authenticateWithBiometrics() 23 | } 24 | 25 | public protocol PasscodeLockTypeDelegate: class { 26 | 27 | func passcodeLockDidSucceed(_ lock: PasscodeLockType) 28 | func passcodeLockDidFail(_ lock: PasscodeLockType) 29 | func passcodeLockDidChangeState(_ lock: PasscodeLockType) 30 | func passcodeLock(_ lock: PasscodeLockType, addedSignAtIndex index: Int) 31 | func passcodeLock(_ lock: PasscodeLockType, removedSignAtIndex index: Int) 32 | } 33 | -------------------------------------------------------------------------------- /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 | var passcode: [String]? {get} 15 | 16 | func savePasscode(_ passcode: [String]) 17 | func deletePasscode() 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 | 34 | 40 | 41 | 42 | 43 | 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 | 143 | 171 | 196 | 221 | 246 | 271 | 296 | 321 | 346 | 371 | 381 | 394 | 404 | 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 | -------------------------------------------------------------------------------- /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 | 14 | @IBInspectable 15 | open var passcodeSign: String = "1" 16 | 17 | @IBInspectable 18 | open var borderColor: UIColor = UIColor.white { 19 | didSet { 20 | setupView() 21 | } 22 | } 23 | 24 | @IBInspectable 25 | open var borderRadius: CGFloat = 30 { 26 | didSet { 27 | setupView() 28 | } 29 | } 30 | 31 | @IBInspectable 32 | open var highlightBackgroundColor: UIColor = UIColor.clear { 33 | didSet { 34 | setupView() 35 | } 36 | } 37 | 38 | public override init(frame: CGRect) { 39 | 40 | super.init(frame: frame) 41 | 42 | setupView() 43 | setupActions() 44 | } 45 | 46 | public required init?(coder aDecoder: NSCoder) { 47 | 48 | super.init(coder: aDecoder) 49 | 50 | setupActions() 51 | } 52 | 53 | open override var intrinsicContentSize : CGSize { 54 | 55 | return CGSize(width: 60, height: 60) 56 | } 57 | 58 | fileprivate var defaultBackgroundColor = UIColor.clear 59 | 60 | fileprivate func setupView() { 61 | 62 | layer.borderWidth = 1 63 | layer.cornerRadius = borderRadius 64 | layer.borderColor = borderColor.cgColor 65 | 66 | if let backgroundColor = backgroundColor { 67 | 68 | defaultBackgroundColor = backgroundColor 69 | } 70 | } 71 | 72 | fileprivate func setupActions() { 73 | 74 | addTarget(self, action: #selector(PasscodeSignButton.handleTouchDown), for: .touchDown) 75 | addTarget(self, action: #selector(PasscodeSignButton.handleTouchUp), for: [.touchUpInside, .touchDragOutside, .touchCancel]) 76 | } 77 | 78 | @objc func handleTouchDown() { 79 | 80 | animateBackgroundColor(highlightBackgroundColor) 81 | } 82 | 83 | @objc func handleTouchUp() { 84 | 85 | animateBackgroundColor(defaultBackgroundColor) 86 | } 87 | 88 | fileprivate func animateBackgroundColor(_ color: UIColor) { 89 | 90 | UIView.animate( 91 | withDuration: 0.3, 92 | delay: 0.0, 93 | usingSpringWithDamping: 1, 94 | initialSpringVelocity: 0.0, 95 | options: [.allowUserInteraction, .beginFromCurrentState], 96 | animations: { 97 | 98 | self.backgroundColor = color 99 | }, 100 | completion: nil 101 | ) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /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 inactiveColor: UIColor = UIColor.white { 22 | didSet { 23 | setupView() 24 | } 25 | } 26 | 27 | @IBInspectable 28 | open var activeColor: UIColor = UIColor.gray { 29 | didSet { 30 | setupView() 31 | } 32 | } 33 | 34 | @IBInspectable 35 | open var errorColor: UIColor = UIColor.red { 36 | didSet { 37 | setupView() 38 | } 39 | } 40 | 41 | public override init(frame: CGRect) { 42 | 43 | super.init(frame: frame) 44 | 45 | setupView() 46 | } 47 | 48 | public required init?(coder aDecoder: NSCoder) { 49 | 50 | super.init(coder: aDecoder) 51 | } 52 | 53 | open override var intrinsicContentSize : CGSize { 54 | 55 | return CGSize(width: 16, height: 16) 56 | } 57 | 58 | fileprivate func setupView() { 59 | 60 | layer.cornerRadius = 8 61 | layer.borderWidth = 1 62 | layer.borderColor = activeColor.cgColor 63 | backgroundColor = inactiveColor 64 | } 65 | 66 | fileprivate func colorsForState(_ state: State) -> (backgroundColor: UIColor, borderColor: UIColor) { 67 | 68 | switch state { 69 | case .inactive: return (inactiveColor, activeColor) 70 | case .active: return (activeColor, activeColor) 71 | case .error: return (errorColor, errorColor) 72 | } 73 | } 74 | 75 | open func animateState(_ state: State) { 76 | 77 | let colors = colorsForState(state) 78 | 79 | UIView.animate( 80 | withDuration: 0.5, 81 | delay: 0, 82 | usingSpringWithDamping: 1, 83 | initialSpringVelocity: 0, 84 | options: [], 85 | animations: { 86 | 87 | self.backgroundColor = colors.backgroundColor 88 | self.layer.borderColor = colors.borderColor.cgColor 89 | 90 | }, 91 | completion: nil 92 | ) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /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 | /* Touch ID Reason */ 30 | "PasscodeLockTouchIDReason" = "Authentication required to proceed"; 31 | 32 | /* Touch ID Fallback Button */ 33 | "PasscodeLockTouchIDButton" = "Enter Passcode"; -------------------------------------------------------------------------------- /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 = CustomPasscodeLockPresenter(mainWindow: self.window, configuration: configuration) 21 | 22 | return presenter 23 | }() 24 | 25 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 26 | 27 | let _ = passcodeLockPresenter 28 | 29 | return true 30 | } 31 | 32 | func applicationWillResignActive(_ application: UIApplication) { 33 | // 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. 34 | // 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. 35 | } 36 | 37 | func applicationDidEnterBackground(_ application: UIApplication) { 38 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 39 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 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" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /PasscodeLockDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PasscodeLockDemo/Assets.xcassets/fake-logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "TouchID_App_icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PasscodeLockDemo/Assets.xcassets/fake-logo.imageset/TouchID_App_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velikanov/SwiftPasscodeLock/6b2ee8fae29568735a895f016f40fb634a8d7c25/PasscodeLockDemo/Assets.xcassets/fake-logo.imageset/TouchID_App_icon.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 39 | 46 | 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 | -------------------------------------------------------------------------------- /PasscodeLockDemo/CustomPasscodeLockPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPasscodeLockPresenter.swift 3 | // PasscodeLock 4 | // 5 | // Created by Chris Ziogas on 19/12/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import PasscodeLock 11 | 12 | class CustomPasscodeLockPresenter: PasscodeLockPresenter { 13 | 14 | fileprivate let notificationCenter: NotificationCenter 15 | 16 | fileprivate let splashView: UIView 17 | 18 | var isFreshAppLaunch = true 19 | 20 | init(mainWindow window: UIWindow?, configuration: PasscodeLockConfigurationType) { 21 | 22 | notificationCenter = NotificationCenter.default 23 | 24 | splashView = LockSplashView() 25 | 26 | // TIP: you can set your custom viewController that has added functionality in a custom .xib too 27 | let passcodeLockVC = PasscodeLockViewController(state: .enterPasscode, configuration: configuration) 28 | 29 | super.init(mainWindow: window, configuration: configuration, viewController: passcodeLockVC) 30 | 31 | // add notifications observers 32 | notificationCenter.addObserver( 33 | self, 34 | selector: #selector(CustomPasscodeLockPresenter.applicationDidLaunched), 35 | name: NSNotification.Name.UIApplicationDidFinishLaunching, 36 | object: nil 37 | ) 38 | 39 | notificationCenter.addObserver( 40 | self, 41 | selector: #selector(CustomPasscodeLockPresenter.applicationDidEnterBackground), 42 | name: NSNotification.Name.UIApplicationDidEnterBackground, 43 | object: nil 44 | ) 45 | 46 | notificationCenter.addObserver( 47 | self, 48 | selector: #selector(CustomPasscodeLockPresenter.applicationDidBecomeActive), 49 | name: NSNotification.Name.UIApplicationDidBecomeActive, 50 | object: nil 51 | ) 52 | } 53 | 54 | deinit { 55 | // remove all notfication observers 56 | notificationCenter.removeObserver(self) 57 | } 58 | 59 | @objc dynamic func applicationDidLaunched() -> Void { 60 | 61 | // start the Pin Lock presenter 62 | passcodeLockVC.successCallback = { [weak self] _ in 63 | 64 | // we can set isFreshAppLaunch to false 65 | self?.isFreshAppLaunch = false 66 | } 67 | 68 | presentPasscodeLock() 69 | } 70 | 71 | @objc dynamic func applicationDidEnterBackground() -> Void { 72 | 73 | // present PIN lock 74 | presentPasscodeLock() 75 | 76 | // add splashView for iOS app background swithcer 77 | addSplashView() 78 | } 79 | 80 | @objc dynamic func applicationDidBecomeActive() -> Void { 81 | 82 | // remove splashView for iOS app background swithcer 83 | removeSplashView() 84 | } 85 | 86 | fileprivate func addSplashView() { 87 | 88 | // add splashView for iOS app background swithcer 89 | if isPasscodePresented { 90 | passcodeLockVC.view.addSubview(splashView) 91 | } else { 92 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 93 | appDelegate.window?.addSubview(splashView) 94 | } 95 | } 96 | } 97 | 98 | fileprivate func removeSplashView() { 99 | 100 | // remove splashView for iOS app background swithcer 101 | splashView.removeFromSuperview() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /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/LockSplashView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LockSplashView.swift 3 | // PasscodeLock 4 | // 5 | // Created by Chris Ziogas on 19/12/15. 6 | // Copyright © 2015 Yanko Dimitrov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | open class LockSplashView: UIView { 12 | 13 | fileprivate lazy var logo: UIImageView = { 14 | 15 | let image = UIImage(named: "fake-logo") 16 | let view = UIImageView(image: image) 17 | view.contentMode = UIViewContentMode.center 18 | view.translatesAutoresizingMaskIntoConstraints = false 19 | 20 | return view 21 | }() 22 | 23 | /////////////////////////////////////////////////////// 24 | // MARK: - Initializers 25 | /////////////////////////////////////////////////////// 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | 30 | backgroundColor = UIColor.white 31 | 32 | addSubview(logo) 33 | setupLayout() 34 | } 35 | 36 | convenience init() { 37 | self.init(frame: UIScreen.main.bounds) 38 | } 39 | 40 | public required init?(coder aDecoder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | /////////////////////////////////////////////////////// 45 | // MARK: - Layout 46 | /////////////////////////////////////////////////////// 47 | 48 | fileprivate func setupLayout() { 49 | 50 | let views = ["logo": logo] 51 | 52 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[logo]|", options: [], metrics: nil, views: views)) 53 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[logo]", options: [], metrics: nil, views: views)) 54 | 55 | addConstraint(NSLayoutConstraint(item: logo, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)) 56 | addConstraint(NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: logo, attribute: .centerY, multiplier: 1, constant: 0)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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 | 14 | let repository: PasscodeRepositoryType 15 | var isTouchIDAllowed = true 16 | let shouldRequestTouchIDImmediately = true 17 | var touchIdReason: String? = nil 18 | 19 | init(repository: PasscodeRepositoryType) { 20 | 21 | self.repository = repository 22 | } 23 | 24 | init() { 25 | 26 | self.repository = UserDefaultsPasscodeRepository() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /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 | 14 | @IBOutlet weak var passcodeSwitch: UISwitch! 15 | @IBOutlet weak var changePasscodeButton: UIButton! 16 | @IBOutlet weak var testTextField: UITextField! 17 | @IBOutlet weak var testActivityButton: UIButton! 18 | 19 | fileprivate let configuration: PasscodeLockConfigurationType 20 | 21 | init(configuration: PasscodeLockConfigurationType) { 22 | 23 | self.configuration = configuration 24 | 25 | super.init(nibName: nil, bundle: nil) 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | 30 | let repository = UserDefaultsPasscodeRepository() 31 | configuration = PasscodeLockConfiguration(repository: repository) 32 | 33 | super.init(coder: aDecoder) 34 | } 35 | 36 | // MARK: - View 37 | 38 | override func viewWillAppear(_ animated: Bool) { 39 | super.viewWillAppear(animated) 40 | 41 | updatePasscodeView() 42 | } 43 | 44 | func updatePasscodeView() { 45 | 46 | let hasPasscode = configuration.repository.hasPasscode 47 | 48 | changePasscodeButton.isHidden = !hasPasscode 49 | passcodeSwitch.isOn = hasPasscode 50 | } 51 | 52 | // MARK: - Actions 53 | 54 | @IBAction func passcodeSwitchValueChange(_ sender: UISwitch) { 55 | 56 | let passcodeVC: PasscodeLockViewController 57 | 58 | if passcodeSwitch.isOn { 59 | 60 | passcodeVC = PasscodeLockViewController(state: .setPasscode, configuration: configuration) 61 | 62 | } else { 63 | 64 | passcodeVC = PasscodeLockViewController(state: .removePasscode, configuration: configuration) 65 | 66 | passcodeVC.successCallback = { [weak self] lock in 67 | 68 | lock.repository.deletePasscode() 69 | 70 | self?.updatePasscodeView() 71 | } 72 | } 73 | 74 | present(passcodeVC, animated: true, completion: nil) 75 | } 76 | 77 | @IBAction func changePasscodeButtonTap(_ sender: UIButton) { 78 | 79 | let repo = UserDefaultsPasscodeRepository() 80 | let config = PasscodeLockConfiguration(repository: repo) 81 | 82 | let passcodeLock = PasscodeLockViewController(state: .changePasscode, configuration: config) 83 | 84 | present(passcodeLock, animated: true, completion: nil) 85 | } 86 | 87 | @IBAction func testAlertButtonTap(_ sender: UIButton) { 88 | 89 | let alertVC = UIAlertController(title: "Test", message: "", preferredStyle: .alert) 90 | 91 | alertVC.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 92 | 93 | present(alertVC, animated: true, completion: nil) 94 | 95 | } 96 | 97 | @IBAction func testActivityButtonTap(_ sender: UIButton) { 98 | 99 | let activityVC = UIActivityViewController(activityItems: ["Test"], applicationActivities: nil) 100 | 101 | activityVC.popoverPresentationController?.sourceView = testActivityButton 102 | activityVC.popoverPresentationController?.sourceRect = CGRect(x: 10, y: 20, width: 0, height: 0) 103 | 104 | present(activityVC, animated: true, completion: nil) 105 | } 106 | 107 | @IBAction func dismissKeyboard() { 108 | 109 | testTextField.resignFirstResponder() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /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 | class UserDefaultsPasscodeRepository: PasscodeRepositoryType { 13 | 14 | fileprivate let passcodeKey = "passcode.lock.passcode" 15 | 16 | fileprivate lazy var defaults: UserDefaults = { 17 | 18 | return UserDefaults.standard 19 | }() 20 | 21 | var hasPasscode: Bool { 22 | 23 | if passcode != nil { 24 | return true 25 | } 26 | 27 | return false 28 | } 29 | 30 | var passcode: [String]? { 31 | 32 | return defaults.value(forKey: passcodeKey) as? [String] ?? nil 33 | } 34 | 35 | func savePasscode(_ passcode: [String]) { 36 | 37 | defaults.set(passcode, forKey: passcodeKey) 38 | defaults.synchronize() 39 | } 40 | 41 | func deletePasscode() { 42 | 43 | defaults.removeObject(forKey: passcodeKey) 44 | defaults.synchronize() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 changeStateTo(_ state: PasscodeLockStateType) { 37 | 38 | lockState = state 39 | changeStateCalled = true 40 | delegate?.passcodeLockDidChangeState(self) 41 | } 42 | 43 | func authenticateWithBiometrics() { 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 | 13 | let repository: PasscodeRepositoryType 14 | var isTouchIDAllowed = false 15 | let maximumInccorectPasscodeAttempts = 3 16 | let shouldRequestTouchIDImmediately = false 17 | var touchIdReason: String? = nil 18 | 19 | init(repository: PasscodeRepositoryType) { 20 | 21 | self.repository = repository 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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, addedSignAtIndex index: Int) {} 17 | func passcodeLock(_ lock: PasscodeLockType, removedSignAtIndex 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 | var passcode: [String]? { return fakePasscode } 15 | 16 | var fakePasscode = ["1", "2", "3", "4"] 17 | 18 | var savePasscodeCalled = false 19 | var savedPasscode = [String]() 20 | 21 | func savePasscode(_ passcode: [String]) { 22 | 23 | savePasscodeCalled = true 24 | savedPasscode = passcode 25 | } 26 | 27 | func deletePasscode() { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | let title = "A" 14 | let description = "B" 15 | let 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 acceptPasscode(_ passcode: [String], fromLock 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.acceptPasscode(repository.fakePasscode, fromLock: 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.acceptPasscode(["0", "0", "0", "0"], fromLock: 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 = ["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.acceptPasscode(passcodeToConfirm, fromLock: passcodeLock) 45 | 46 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is correct") 47 | } 48 | 49 | func testAcceptCorrectPasscodeWillSaveThePasscode() { 50 | 51 | passcodeState.acceptPasscode(passcodeToConfirm, fromLock: 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.acceptPasscode(["1", "2"], fromLock: 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: String) { 17 | 18 | let center = NotificationCenter.default 19 | 20 | center.addObserver(self, selector: #selector(NotificaionObserver.handle(notification:)), name: NSNotification.Name(rawValue: notification), object: nil) 21 | } 22 | 23 | @objc func handle(notification: NSNotification) { 24 | 25 | called = true 26 | callCounter += 1 27 | } 28 | } 29 | 30 | class EnterPasscodeStateTests: XCTestCase { 31 | 32 | var passcodeLock: FakePasscodeLock! 33 | var passcodeState: EnterPasscodeState! 34 | var repository: FakePasscodeRepository! 35 | 36 | override func setUp() { 37 | super.setUp() 38 | 39 | repository = FakePasscodeRepository() 40 | 41 | let config = FakePasscodeLockConfiguration(repository: repository) 42 | 43 | passcodeState = EnterPasscodeState() 44 | passcodeLock = FakePasscodeLock(state: passcodeState, configuration: config) 45 | } 46 | 47 | func testAcceptCorrectPasscode() { 48 | 49 | class MockDelegate: FakePasscodeLockDelegate { 50 | 51 | var called = false 52 | 53 | override func passcodeLockDidSucceed(_ lock: PasscodeLockType) { 54 | 55 | called = true 56 | } 57 | } 58 | 59 | let delegate = MockDelegate() 60 | 61 | passcodeLock.delegate = delegate 62 | passcodeState.acceptPasscode(repository.fakePasscode, fromLock: passcodeLock) 63 | 64 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is correct") 65 | } 66 | 67 | func testAcceptIncorrectPasscode() { 68 | 69 | class MockDelegate: FakePasscodeLockDelegate { 70 | 71 | var called = false 72 | 73 | override func passcodeLockDidFail(_ lock: PasscodeLockType) { 74 | 75 | called = true 76 | } 77 | } 78 | 79 | let delegate = MockDelegate() 80 | 81 | passcodeLock.delegate = delegate 82 | passcodeState.acceptPasscode(["0", "0", "0", "0"], fromLock: passcodeLock) 83 | 84 | XCTAssertEqual(delegate.called, true, "Should call the delegate when the passcode is incorrect") 85 | } 86 | 87 | func testIncorrectPasscodeNotification() { 88 | 89 | let observer = NotificaionObserver() 90 | 91 | observer.observe(notification: PasscodeLockIncorrectPasscodeNotification) 92 | 93 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 94 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 95 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 96 | 97 | XCTAssertEqual(observer.called, true, "Should send a notificaiton when the maximum number of incorrect attempts is reached") 98 | } 99 | 100 | func testIncorrectPasscodeSendNotificationOnce() { 101 | 102 | let observer = NotificaionObserver() 103 | 104 | observer.observe(notification: PasscodeLockIncorrectPasscodeNotification) 105 | 106 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 107 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 108 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 109 | 110 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 111 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 112 | passcodeState.acceptPasscode(["0"], fromLock: passcodeLock) 113 | 114 | XCTAssertEqual(observer.callCounter, 1, "Should send the notification only once") 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /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 | 13 | var passcodeLock: PasscodeLock! 14 | var initialState: FakePasscodeState! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | let repository = FakePasscodeRepository() 20 | let config = FakePasscodeLockConfiguration(repository: repository) 21 | 22 | initialState = FakePasscodeState() 23 | passcodeLock = PasscodeLock(state: initialState, configuration: config) 24 | } 25 | 26 | func testSetStateTo() { 27 | 28 | class MockDelegate: FakePasscodeLockDelegate { 29 | 30 | var called = false 31 | 32 | override func passcodeLockDidChangeState(_ lock: PasscodeLockType) { 33 | 34 | called = true 35 | } 36 | } 37 | 38 | let delegate = MockDelegate() 39 | let nextState = FakePasscodeState() 40 | 41 | passcodeLock.delegate = delegate 42 | passcodeLock.changeStateTo(nextState) 43 | 44 | XCTAssertEqual(delegate.called, true, "Should inform the delegate for state changes") 45 | } 46 | 47 | func testAddSign() { 48 | 49 | class MockDelegate: FakePasscodeLockDelegate { 50 | 51 | var called = false 52 | var signIndex = 0 53 | 54 | override func passcodeLock(_ lock: PasscodeLockType, addedSignAtIndex index: Int) { 55 | 56 | called = true 57 | signIndex = index 58 | } 59 | } 60 | 61 | let delegate = MockDelegate() 62 | 63 | passcodeLock.delegate = delegate 64 | passcodeLock.addSign("1") 65 | 66 | XCTAssertEqual(delegate.called, true, "Should inform the delegate for added sign at index") 67 | XCTAssertEqual(delegate.signIndex, 0, "Should return the added sign index") 68 | 69 | passcodeLock.addSign("2") 70 | 71 | XCTAssertEqual(delegate.signIndex, 1, "Should return the added sign index") 72 | } 73 | 74 | func testRemoveSign() { 75 | 76 | class MockDelegate: FakePasscodeLockDelegate { 77 | 78 | var called = false 79 | var signIndex = 0 80 | 81 | override func passcodeLock(_ lock: PasscodeLockType, removedSignAtIndex index: Int) { 82 | 83 | called = true 84 | signIndex = index 85 | } 86 | } 87 | 88 | let delegate = MockDelegate() 89 | 90 | passcodeLock.delegate = delegate 91 | passcodeLock.addSign("1") 92 | passcodeLock.addSign("2") 93 | 94 | passcodeLock.removeSign() 95 | 96 | XCTAssertEqual(delegate.called, true, "Should inform the delegate for removed sign at index") 97 | XCTAssertEqual(delegate.signIndex, 1, "Should return the removed sign index") 98 | 99 | passcodeLock.removeSign() 100 | XCTAssertEqual(delegate.signIndex, 0, "Should return the removed sign index") 101 | } 102 | 103 | func testCallStateToAcceptTheEnteredPasscode() { 104 | 105 | let passcode = ["0", "1", "2", "3"] 106 | 107 | for sign in passcode { 108 | 109 | passcodeLock.addSign(sign) 110 | } 111 | 112 | XCTAssertEqual(initialState.acceptPaccodeCalled, true, "When the passcode length is reached should call the current state to accept the entered passcode") 113 | XCTAssertEqual(initialState.acceptedPasscode, passcode, "Should return the entered passcode") 114 | XCTAssertEqual(initialState.numberOfAcceptedPasscodes, 1, "Should call the accept passcode only once") 115 | } 116 | 117 | func testResetSigns() { 118 | 119 | let passcodeOne = ["0", "1", "2", "3"] 120 | let passcodeTwo = ["9", "8", "7", "6"] 121 | 122 | for sign in passcodeOne { 123 | 124 | passcodeLock.addSign(sign) 125 | } 126 | 127 | for sign in passcodeTwo { 128 | 129 | passcodeLock.addSign(sign) 130 | } 131 | 132 | XCTAssertEqual(initialState.numberOfAcceptedPasscodes, 2, "Should call the accept passcode twice") 133 | XCTAssertEqual(initialState.acceptedPasscode, passcodeTwo, "Shpuld return the last entered passcode") 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /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.acceptPasscode(repository.fakePasscode, fromLock: 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 | A Swift implementation of passcode lock for iOS with TouchID authentication. 3 | 4 | Originally created by [@yankodimitrov](https://github.com/yankodimitrov/SwiftPasscodeLock), hope you're doing well. 5 | 6 | 7 | 8 | ## Installation 9 | PasscodeLock requires Swift 2.0 and Xcode 7 10 | 11 | ### [CocoaPods](http://cocoapods.org/) 12 | 13 | #### Podfile 14 | 15 | To integrate PasscodeLock into your Xcode project using CocoaPods, specify it in your `Podfile`: 16 | 17 | ```ruby 18 | source 'https://github.com/CocoaPods/Specs.git' 19 | platform :ios, '8.0' 20 | 21 | pod 'PasscodeLock', '~> 1.0.2' 22 | ``` 23 | 24 | Then, run the following command: 25 | 26 | ```bash 27 | $ pod install 28 | ``` 29 | 30 | ### [Carthage](https://github.com/Carthage/Carthage) 31 | 32 | Add the following line to your [Cartfile](https://github.com/carthage/carthage) 33 | ```swift 34 | github "velikanov/SwiftPasscodeLock" 35 | ``` 36 | ## Usage 37 | 38 | - Create an implementation of the `PasscodeRepositoryType` protocol. 39 | 40 | ```swift 41 | import UIKit 42 | import PasscodeLock 43 | 44 | class PasscodeRepository: PasscodeRepositoryType { 45 | 46 | var hasPasscode: Bool = true 47 | var passcode: [String]? 48 | 49 | func savePasscode(passcode: [String]) {} 50 | 51 | func deletePasscode() {} 52 | 53 | } 54 | ``` 55 | 56 | - 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 user will reach that number of incorrect passcode attempts a notification with name `PasscodeLockIncorrectPasscodeNotification` will be posted on the default `NSNotificationCenter`. 57 | 58 | ```swift 59 | import UIKit 60 | import PasscodeLock 61 | 62 | class PasscodeLockConfiguration: PasscodeLockConfigurationType { 63 | let repository: PasscodeRepositoryType 64 | var passcodeLength = 4 // Specify the required amount of passcode digits 65 | var isTouchIDAllowed = true // Enable Touch ID 66 | var shouldRequestTouchIDImmediately = true // Use Touch ID authentication immediately 67 | var maximumInccorectPasscodeAttempts = 3 // Maximum incorrect passcode attempts 68 | 69 | init(repository: PasscodeRepositoryType) { 70 | self.repository = repository 71 | } 72 | 73 | init() { 74 | self.repository = PasscodeRepository() // The repository that was created earlier 75 | } 76 | } 77 | ``` 78 | 79 | - 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. 80 | 81 | - Allow your users to set a passcode by presenting the `PasscodeLockViewController` in `.SetPasscode` state: 82 | ```swift 83 | let configuration = ... // your implementation of the PasscodeLockConfigurationType protocol 84 | 85 | let passcodeViewController = PasscodeLockViewController(state: .SetPasscode, configuration: configuration) 86 | 87 | presentViewController(passcodeViewController, animated: true, completion: nil) 88 | ``` 89 | 90 | You can present the `PasscodeLockViewController` in one of the four initial states using the `LockState` enumeration options: `.EnterPasscode`, `.SetPasscode`, `.ChangePasscode`, `.RemovePasscode`. 91 | 92 | Also you can set the initial passcode lock state to your own implementation of the `PasscodeLockStateType` protocol. 93 | 94 | ## Customization 95 | 96 | ### Custom Design 97 | 98 | 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. 99 | 100 | Keep in mind that when using custom classes that are defined in another module, you'll need to set the Module field to that module's name in the Identity Inspector: 101 | 102 | 103 | 104 | Then connect the `view` outlet to the view of your `xib` file and make sure to conenct the remaining `IBOutlet`s and `IBAction`s. 105 | 106 | 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: 107 | 108 | 109 | 110 | ### Localization 111 | 112 | 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. 113 | 114 | ## Demo App 115 | 116 | The demo app comes with a simple implementation of the `PasscodeRepositoryType` protocol that is using the **NSUserDefaults** to store and 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. 117 | -------------------------------------------------------------------------------- /identity-inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velikanov/SwiftPasscodeLock/6b2ee8fae29568735a895f016f40fb634a8d7c25/identity-inspector.png -------------------------------------------------------------------------------- /passcode-lock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velikanov/SwiftPasscodeLock/6b2ee8fae29568735a895f016f40fb634a8d7c25/passcode-lock.gif -------------------------------------------------------------------------------- /passcode-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velikanov/SwiftPasscodeLock/6b2ee8fae29568735a895f016f40fb634a8d7c25/passcode-view.png --------------------------------------------------------------------------------